@carto/ps-react-ui 6.3.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions-DyWNwvLu.js +1272 -0
- package/dist/actions-DyWNwvLu.js.map +1 -0
- package/dist/{category-DULnoxvk.js → category-BHBd5Pcc.js} +262 -245
- package/dist/category-BHBd5Pcc.js.map +1 -0
- package/dist/scale-Bhx8HKEB.js +29 -0
- package/dist/scale-Bhx8HKEB.js.map +1 -0
- package/dist/types/widgets/actions/index.d.ts +1 -1
- package/dist/types/widgets/actions/relative-data/index.d.ts +1 -0
- package/dist/types/widgets/actions/relative-data/labels.d.ts +3 -0
- package/dist/types/widgets/actions/relative-data/relative-data.d.ts +16 -2
- package/dist/types/widgets/actions/relative-data/scale.d.ts +26 -0
- package/dist/types/widgets/actions/relative-data/style.d.ts +12 -0
- package/dist/types/widgets/category/category.d.ts +2 -1
- package/dist/types/widgets/index.d.ts +1 -1
- package/dist/widgets/actions.js +1 -1
- package/dist/widgets/bar.js +80 -79
- package/dist/widgets/bar.js.map +1 -1
- package/dist/widgets/category.js +1 -1
- package/dist/widgets/histogram.js +104 -103
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/scatterplot.js +116 -115
- package/dist/widgets/scatterplot.js.map +1 -1
- package/dist/widgets/timeseries.js +86 -85
- package/dist/widgets/timeseries.js.map +1 -1
- package/dist/widgets.js +2 -2
- package/dist/widgets.js.map +1 -1
- package/package.json +1 -1
- package/src/widgets/actions/index.ts +1 -0
- package/src/widgets/actions/relative-data/index.ts +1 -0
- package/src/widgets/actions/relative-data/labels.ts +6 -0
- package/src/widgets/actions/relative-data/relative-data.test.tsx +78 -1
- package/src/widgets/actions/relative-data/relative-data.tsx +137 -20
- package/src/widgets/actions/relative-data/scale.ts +48 -0
- package/src/widgets/actions/relative-data/style.ts +10 -0
- package/src/widgets/bar/options.test.ts +16 -0
- package/src/widgets/bar/options.ts +6 -2
- package/src/widgets/category/category.relative-data.test.tsx +51 -63
- package/src/widgets/category/category.tsx +25 -11
- package/src/widgets/histogram/options.ts +6 -2
- package/src/widgets/index.ts +1 -0
- package/src/widgets/scatterplot/options.test.ts +12 -0
- package/src/widgets/scatterplot/options.ts +18 -4
- package/src/widgets/timeseries/options.ts +6 -2
- package/dist/actions-CkxKe8-J.js +0 -1160
- package/dist/actions-CkxKe8-J.js.map +0 -1
- package/dist/category-DULnoxvk.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"category-BHBd5Pcc.js","names":["styles","{\n value,\n maxValue,\n minValue = 0,\n color,\n size = 'small',\n zeroTick = true,\n}: CategoryBarProps","minValue = 0","size = 'small'","zeroTick = true","value < 0\n ? { borderTopRightRadius: 0, borderBottomRightRadius: 0 }\n : { borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }","{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: color,\n // The end that grows out of the zero is square; only the far\n // end keeps the rounded corners.\n ...(value < 0\n ? { borderTopRightRadius: 0, borderBottomRightRadius: 0 }\n : { borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }),\n }","`${leftPct}%`","`${pct}%`","<Box\n sx={styles.barFill[size]}\n style={{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: color,\n // The end that grows out of the zero is square; only the far\n // end keeps the rounded corners.\n ...(value < 0\n ? { borderTopRightRadius: 0, borderBottomRightRadius: 0 }\n : { borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }),\n }}\n // Tag the fill so an ancestor row's `:hover` selector can target\n // it (`&:hover [data-bar-fill]`) — the brightening affordance now\n // fires from anywhere in the row, not only the bar track.\n data-bar-fill='true'\n />","styles.barFill[size]","<Box sx={styles.bar[size]} data-size={size}>\n <Box\n sx={styles.barFill[size]}\n style={{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: color,\n // The end that grows out of the zero is square; only the far\n // end keeps the rounded corners.\n ...(value < 0\n ? { borderTopRightRadius: 0, borderBottomRightRadius: 0 }\n : { borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }),\n }}\n // Tag the fill so an ancestor row's `:hover` selector can target\n // it (`&:hover [data-bar-fill]`) — the brightening affordance now\n // fires from anywhere in the row, not only the bar track.\n data-bar-fill='true'\n />\n </Box>","styles.bar[size]","<ZeroTickWrap zeroPct={hasZeroTick ? (-minValue / range) * 100 : undefined}>\n <Box sx={styles.bar[size]} data-size={size}>\n <Box\n sx={styles.barFill[size]}\n style={{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: color,\n // The end that grows out of the zero is square; only the far\n // end keeps the rounded corners.\n ...(value < 0\n ? { borderTopRightRadius: 0, borderBottomRightRadius: 0 }\n : { borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }),\n }}\n // Tag the fill so an ancestor row's `:hover` selector can target\n // it (`&:hover [data-bar-fill]`) — the brightening affordance now\n // fires from anywhere in the row, not only the bar track.\n data-bar-fill='true'\n />\n </Box>\n </ZeroTickWrap>","hasZeroTick ? (-minValue / range) * 100 : undefined","{\n zeroPct,\n children,\n}: {\n zeroPct: number | undefined\n children: ReactNode\n}","<Box\n aria-hidden\n data-zero-tick='true'\n sx={styles.zeroTick}\n style={{ left: `${zeroPct}%` }}\n />","`${zeroPct}%`","<Box sx={styles.barWrap}>\n <Box\n aria-hidden\n data-zero-tick='true'\n sx={styles.zeroTick}\n style={{ left: `${zeroPct}%` }}\n />\n {children}\n </Box>","_c","styles","{\n name,\n displayName,\n value,\n maxValue,\n minValue,\n color,\n formatter,\n selected,\n onToggle,\n size,\n}: CategoryRowSingleProps","(e: KeyboardEvent<HTMLDivElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle(name)\n }\n }","() => onToggle(name)","<Typography variant='body2' sx={styles.name}>\n {displayName ?? name}\n </Typography>","displayName ?? name","formatter(value)","<Typography variant='body2' sx={styles.value}>\n {formatter(value)}\n </Typography>","<Box sx={styles.rowHeader}>\n <Typography variant='body2' sx={styles.name}>\n {displayName ?? name}\n </Typography>\n <Typography variant='body2' sx={styles.value}>\n {formatter(value)}\n </Typography>\n </Box>","<CategoryBar\n value={value}\n maxValue={maxValue}\n minValue={minValue}\n color={color}\n size={size}\n />","<Box\n role='button'\n aria-pressed={selected}\n tabIndex={0}\n onClick={() => onToggle(name)}\n onKeyDown={handleKey}\n sx={styles.rowSingle}\n >\n <Box sx={styles.rowHeader}>\n <Typography variant='body2' sx={styles.name}>\n {displayName ?? name}\n </Typography>\n <Typography variant='body2' sx={styles.value}>\n {formatter(value)}\n </Typography>\n </Box>\n <CategoryBar\n value={value}\n maxValue={maxValue}\n minValue={minValue}\n color={color}\n size={size}\n />\n </Box>","_c","styles","{\n name,\n displayName,\n values,\n colors,\n maxValue,\n minValue,\n formatter,\n selected,\n onToggle,\n size,\n}: CategoryRowMultiProps","(e: KeyboardEvent<HTMLDivElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle(name)\n }\n }","() => onToggle(name)","<Typography variant='body2' sx={styles.multiName}>\n {displayName ?? name}\n </Typography>","displayName ?? name","min < 0 && maxValue > 0 && (\n <Box\n aria-hidden\n sx={styles.multiZeroArea}\n // Row 1 is the label; the bars fill the next `values.length` rows.\n // Placed before the bars so their tracks paint over the line.\n style={{ gridRow: `2 / span ${values.length}` }}\n >\n <Box\n data-zero-tick='true'\n sx={styles.zeroTick}\n style={{ left: `${(-min / (maxValue - min)) * 100}%` }}\n />\n </Box>\n )","length","values.map((v, i) => (\n // Multi-bar rows iterate per series — the bar belongs to series `i`,\n // and the row is keyed on the parent's `name`, so a composite of\n // `name` + the series index is stable across reorders.\n <Box key={`${name}-series-${i}`} sx={styles.multiBarRow}>\n <Box sx={styles.multiBarContainer}>\n <CategoryBar\n value={v}\n maxValue={maxValue}\n minValue={minValue}\n color={colors[i] ?? ''}\n size={size}\n zeroTick={false}\n />\n </Box>\n <Typography variant='body2' sx={styles.value}>\n {formatter(v)}\n </Typography>\n </Box>\n ))","(v, i) => (\n // Multi-bar rows iterate per series — the bar belongs to series `i`,\n // and the row is keyed on the parent's `name`, so a composite of\n // `name` + the series index is stable across reorders.\n <Box key={`${name}-series-${i}`} sx={styles.multiBarRow}>\n <Box sx={styles.multiBarContainer}>\n <CategoryBar\n value={v}\n maxValue={maxValue}\n minValue={minValue}\n color={colors[i] ?? ''}\n size={size}\n zeroTick={false}\n />\n </Box>\n <Typography variant='body2' sx={styles.value}>\n {formatter(v)}\n </Typography>\n </Box>\n )","<Box\n role='button'\n aria-pressed={selected}\n tabIndex={0}\n onClick={() => onToggle(name)}\n onKeyDown={handleKey}\n sx={styles.rowMulti}\n >\n <Typography variant='body2' sx={styles.multiName}>\n {displayName ?? name}\n </Typography>\n {min < 0 && maxValue > 0 && (\n <Box\n aria-hidden\n sx={styles.multiZeroArea}\n // Row 1 is the label; the bars fill the next `values.length` rows.\n // Placed before the bars so their tracks paint over the line.\n style={{ gridRow: `2 / span ${values.length}` }}\n >\n <Box\n data-zero-tick='true'\n sx={styles.zeroTick}\n style={{ left: `${(-min / (maxValue - min)) * 100}%` }}\n />\n </Box>\n )}\n {values.map((v, i) => (\n // Multi-bar rows iterate per series — the bar belongs to series `i`,\n // and the row is keyed on the parent's `name`, so a composite of\n // `name` + the series index is stable across reorders.\n <Box key={`${name}-series-${i}`} sx={styles.multiBarRow}>\n <Box sx={styles.multiBarContainer}>\n <CategoryBar\n value={v}\n maxValue={maxValue}\n minValue={minValue}\n color={colors[i] ?? ''}\n size={size}\n zeroTick={false}\n />\n </Box>\n <Typography variant='body2' sx={styles.value}>\n {formatter(v)}\n </Typography>\n </Box>\n ))}\n </Box>","_c","styles","{\n values,\n colors,\n maxValue,\n zeroPct = 0,\n size = 'small',\n}: CategoryBarStackedProps","zeroPct = 0","size = 'small'","values.map((v, i) => {\n if (v === 0 || maxValue <= 0) return null\n const leftPct = zeroPct + (lefts[i]! / maxValue) * 100\n const pct = (Math.abs(v) / maxValue) * 100\n return (\n <Box\n // Segments are keyed by per-series colour (the only stable\n // distinguishing field on this side) plus the segment index for\n // when callers reuse palette colours across series.\n key={`seg-${colors[i] ?? 'none'}-${i}`}\n sx={styles.stackedSegment[size]}\n style={{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: colors[i] ?? '',\n }}\n data-bar-fill='true'\n />\n )\n })","(v, i) => {\n if (v === 0 || maxValue <= 0) return null\n const leftPct = zeroPct + (lefts[i]! / maxValue) * 100\n const pct = (Math.abs(v) / maxValue) * 100\n return (\n <Box\n // Segments are keyed by per-series colour (the only stable\n // distinguishing field on this side) plus the segment index for\n // when callers reuse palette colours across series.\n key={`seg-${colors[i] ?? 'none'}-${i}`}\n sx={styles.stackedSegment[size]}\n style={{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: colors[i] ?? '',\n }}\n data-bar-fill='true'\n />\n )\n }","v","<Box sx={styles.bar[size]} data-size={size}>\n {values.map((v, i) => {\n if (v === 0 || maxValue <= 0) return null\n const leftPct = zeroPct + (lefts[i]! / maxValue) * 100\n const pct = (Math.abs(v) / maxValue) * 100\n return (\n <Box\n // Segments are keyed by per-series colour (the only stable\n // distinguishing field on this side) plus the segment index for\n // when callers reuse palette colours across series.\n key={`seg-${colors[i] ?? 'none'}-${i}`}\n sx={styles.stackedSegment[size]}\n style={{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: colors[i] ?? '',\n }}\n data-bar-fill='true'\n />\n )\n })}\n </Box>","styles.bar[size]","<ZeroTickWrap zeroPct={zeroPct > 0 && zeroPct < 100 ? zeroPct : undefined}>\n <Box sx={styles.bar[size]} data-size={size}>\n {values.map((v, i) => {\n if (v === 0 || maxValue <= 0) return null\n const leftPct = zeroPct + (lefts[i]! / maxValue) * 100\n const pct = (Math.abs(v) / maxValue) * 100\n return (\n <Box\n // Segments are keyed by per-series colour (the only stable\n // distinguishing field on this side) plus the segment index for\n // when callers reuse palette colours across series.\n key={`seg-${colors[i] ?? 'none'}-${i}`}\n sx={styles.stackedSegment[size]}\n style={{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: colors[i] ?? '',\n }}\n data-bar-fill='true'\n />\n )\n })}\n </Box>\n </ZeroTickWrap>","zeroPct > 0 && zeroPct < 100 ? zeroPct : undefined","_c","styles","{\n name,\n displayName,\n values,\n colors,\n seriesNames,\n formatter,\n selected,\n onToggle,\n size,\n zeroPct = 0,\n}: CategoryRowStackedProps","zeroPct = 0","(e: KeyboardEvent<HTMLDivElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle(name)\n }\n }","Typography","Box","0","() => onToggle(name)","handleKey","styles.rowStacked","'body2'","styles.stackedTotal","formatter(total)","styles.stackedHeader","<Typography variant='body2' sx={styles.multiName}>\n {displayName ?? name}\n </Typography>","'button'","selected","(a, b) => a + b","displayName ?? name","<Typography variant='body2' sx={styles.stackedTotal}>\n {formatter(total)}\n </Typography>","<Box sx={styles.stackedHeader}>\n <Typography variant='body2' sx={styles.multiName}>\n {displayName ?? name}\n </Typography>\n <Typography variant='body2' sx={styles.stackedTotal}>\n {formatter(total)}\n </Typography>\n </Box>","<CategoryBarStacked\n values={values}\n colors={colors}\n maxValue={scale}\n zeroPct={zeroPct}\n size={size}\n />","values.map((v, i) => (\n // Series name is the stable identity for the breakdown line.\n <Typography\n key={`breakdown-${seriesNames[i]}`}\n variant='body2'\n sx={styles.stackedBreakdownItem}\n >\n {`${seriesNames[i]}: ${formatter(v)}`}\n </Typography>\n ))","(v, i) => (\n // Series name is the stable identity for the breakdown line.\n <Typography\n key={`breakdown-${seriesNames[i]}`}\n variant='body2'\n sx={styles.stackedBreakdownItem}\n >\n {`${seriesNames[i]}: ${formatter(v)}`}\n </Typography>\n )","v","<Box sx={styles.stackedBreakdown}>\n {values.map((v, i) => (\n // Series name is the stable identity for the breakdown line.\n <Typography\n key={`breakdown-${seriesNames[i]}`}\n variant='body2'\n sx={styles.stackedBreakdownItem}\n >\n {`${seriesNames[i]}: ${formatter(v)}`}\n </Typography>\n ))}\n </Box>","<Box\n role='button'\n aria-pressed={selected}\n tabIndex={0}\n onClick={() => onToggle(name)}\n onKeyDown={handleKey}\n sx={styles.rowStacked}\n >\n <Box sx={styles.stackedHeader}>\n <Typography variant='body2' sx={styles.multiName}>\n {displayName ?? name}\n </Typography>\n <Typography variant='body2' sx={styles.stackedTotal}>\n {formatter(total)}\n </Typography>\n </Box>\n <CategoryBarStacked\n values={values}\n colors={colors}\n maxValue={scale}\n zeroPct={zeroPct}\n size={size}\n />\n <Box sx={styles.stackedBreakdown}>\n {values.map((v, i) => (\n // Series name is the stable identity for the breakdown line.\n <Typography\n key={`breakdown-${seriesNames[i]}`}\n variant='body2'\n sx={styles.stackedBreakdownItem}\n >\n {`${seriesNames[i]}: ${formatter(v)}`}\n </Typography>\n ))}\n </Box>\n </Box>","_c","_temp","styles","{\n hiddenCount,\n otherLabel = 'Others',\n otherCountLabel = '{count}',\n onShowAll,\n showAllLabel = 'Show all',\n}: CategoryRowOtherProps","otherLabel = 'Others'","otherCountLabel = '{count}'","showAllLabel = 'Show all'","otherCountLabel.replace('{count}', String(hiddenCount))","<Typography variant='body2' sx={styles.otherCount}>\n {countText}\n </Typography>","<Typography variant='body2' sx={styles.otherLabel}>\n {otherLabel}\n </Typography>","<Box sx={styles.otherRow}>\n <Typography variant='body2' sx={styles.otherLabel}>\n {otherLabel}\n </Typography>\n {count}\n </Box>","(e: KeyboardEvent<HTMLSpanElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onShowAll()\n }\n }","{ ...styles.otherLabel, ...styles.otherLabelButton }","<Typography\n variant='body2'\n component='span'\n role='button'\n tabIndex={0}\n onClick={onShowAll}\n onKeyDown={handleKey}\n sx={{ ...styles.otherLabel, ...styles.otherLabelButton }}\n >\n {otherLabel}\n </Typography>","<Tooltip title={showAllLabel}>\n <Typography\n variant='body2'\n component='span'\n role='button'\n tabIndex={0}\n onClick={onShowAll}\n onKeyDown={handleKey}\n sx={{ ...styles.otherLabel, ...styles.otherLabelButton }}\n >\n {otherLabel}\n </Typography>\n </Tooltip>","<Box sx={styles.otherRow}>\n <Tooltip title={showAllLabel}>\n <Typography\n variant='body2'\n component='span'\n role='button'\n tabIndex={0}\n onClick={onShowAll}\n onKeyDown={handleKey}\n sx={{ ...styles.otherLabel, ...styles.otherLabelButton }}\n >\n {otherLabel}\n </Typography>\n </Tooltip>\n {count}\n </Box>","_c","styles","Tooltip","{\n series,\n colorAt,\n hiddenSeries = NO_HIDDEN_SERIES,\n onToggle,\n}: CategoryLegendProps","hiddenSeries = NO_HIDDEN_SERIES","series.map((s, i) => {\n const hidden = hiddenSeries.has(i)\n return (\n <Box\n key={s.name}\n // `checkbox`/`aria-checked`, not `button`/`aria-pressed` — this\n // toggles a persistent visibility state (closer to a checkbox)\n // AND keeps it out of `getByRole('button')` queries that count\n // category ROWS throughout this widget's test suite.\n role='checkbox'\n aria-checked={!hidden}\n tabIndex={0}\n onClick={() => onToggle?.(i)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle?.(i)\n }\n }}\n sx={hidden ? styles.legendItemHidden : styles.legendItem}\n >\n <Box\n sx={styles.legendDot}\n style={{ backgroundColor: colorAt(i) }}\n />\n <Typography variant='caption' sx={styles.legendLabel}>\n {s.name}\n </Typography>\n </Box>\n )\n })","(s, i) => {\n const hidden = hiddenSeries.has(i)\n return (\n <Box\n key={s.name}\n // `checkbox`/`aria-checked`, not `button`/`aria-pressed` — this\n // toggles a persistent visibility state (closer to a checkbox)\n // AND keeps it out of `getByRole('button')` queries that count\n // category ROWS throughout this widget's test suite.\n role='checkbox'\n aria-checked={!hidden}\n tabIndex={0}\n onClick={() => onToggle?.(i)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle?.(i)\n }\n }}\n sx={hidden ? styles.legendItemHidden : styles.legendItem}\n >\n <Box\n sx={styles.legendDot}\n style={{ backgroundColor: colorAt(i) }}\n />\n <Typography variant='caption' sx={styles.legendLabel}>\n {s.name}\n </Typography>\n </Box>\n )\n }","<Box sx={styles.legend}>\n {series.map((s, i) => {\n const hidden = hiddenSeries.has(i)\n return (\n <Box\n key={s.name}\n // `checkbox`/`aria-checked`, not `button`/`aria-pressed` — this\n // toggles a persistent visibility state (closer to a checkbox)\n // AND keeps it out of `getByRole('button')` queries that count\n // category ROWS throughout this widget's test suite.\n role='checkbox'\n aria-checked={!hidden}\n tabIndex={0}\n onClick={() => onToggle?.(i)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle?.(i)\n }\n }}\n sx={hidden ? styles.legendItemHidden : styles.legendItem}\n >\n <Box\n sx={styles.legendDot}\n style={{ backgroundColor: colorAt(i) }}\n />\n <Typography variant='caption' sx={styles.legendLabel}>\n {s.name}\n </Typography>\n </Box>\n )\n })}\n </Box>","_c","styles","{\n data,\n selection,\n onSelectionChange,\n formatter,\n labelFormatter,\n series,\n maxItems = DEFAULT_MAX_ITEMS,\n labels,\n onShowAll,\n maxOverride,\n minOverride,\n size = 'small',\n stacked = false,\n hiddenSeries: hiddenSeriesProp,\n onHiddenSeriesChange,\n}: CategoryUIProps","maxItems = DEFAULT_MAX_ITEMS","size = 'small'","stacked = false","(n: number) => String(n)","(n: number) => formatSigned(baseFmt, n)","n","selection ?? []","new Set<CategoryKey>(selection ?? [])","useMemo(\n () => new Set<CategoryKey>(selection ?? []),\n [selection],\n )","(name: CategoryKey) => {\n if (!onSelectionChange) return\n const cur = selection ?? []\n const next = cur.includes(name)\n ? cur.filter((n) => n !== name)\n : [...cur, name]\n onSelectionChange(next)\n }","useCallback(\n (name: CategoryKey) => {\n if (!onSelectionChange) return\n const cur = selection ?? []\n const next = cur.includes(name)\n ? cur.filter((n) => n !== name)\n : [...cur, name]\n onSelectionChange(next)\n },\n [onSelectionChange, selection],\n )","Object.values(\n (theme.palette as { qualitative?: { bold?: Record<string, string> } })\n .qualitative?.bold ?? {},\n )","palette","[\n theme.palette.secondary.main,\n ...Object.values(\n (theme.palette as { qualitative?: { bold?: Record<string, string> } })\n .qualitative?.bold ?? {},\n ),\n ]","main","theme.palette.secondary.main","useMemo<readonly string[]>(\n () => [\n theme.palette.secondary.main,\n ...Object.values(\n (theme.palette as { qualitative?: { bold?: Record<string, string> } })\n .qualitative?.bold ?? {},\n ),\n ],\n [theme],\n )","(seriesIndex: number, rowColor?: string): string => {\n const override = rowColor ?? series?.[seriesIndex]?.color\n if (override) return resolveThemeColor(theme, override) ?? override\n return (\n paletteColors[seriesIndex % paletteColors.length] ??\n theme.palette.secondary.main\n )\n }","paletteColors.length","useCallback(\n (seriesIndex: number, rowColor?: string): string => {\n const override = rowColor ?? series?.[seriesIndex]?.color\n if (override) return resolveThemeColor(theme, override) ?? override\n return (\n paletteColors[seriesIndex % paletteColors.length] ??\n theme.palette.secondary.main\n )\n },\n [series, paletteColors, theme],\n )","generateGroupedData(data)","useMemo(() => generateGroupedData(data), [data])","signedDomain(grouped, minOverride, maxOverride)","useMemo(\n () => signedDomain(grouped, minOverride, maxOverride),\n [grouped, minOverride, maxOverride],\n )","(node: HTMLDivElement | null) => {\n if (!node) return\n if (measuredRef.current) return\n if (scrollMode) return\n const h = node.clientHeight\n if (h > 0) {\n measuredRef.current = true\n setNaturalHeight(h)\n }\n }","useCallback(\n (node: HTMLDivElement | null) => {\n if (!node) return\n if (measuredRef.current) return\n if (scrollMode) return\n const h = node.clientHeight\n if (h > 0) {\n measuredRef.current = true\n setNaturalHeight(h)\n }\n },\n [scrollMode],\n )","() => new Set()","categorySeriesKey(series)","(index: number) => {\n // Start from what the user sees, not the stored set: `data` can\n // shrink while the `series` names hold (the reset only watches the\n // names), and a stale index would otherwise count toward the guard\n // below — or make unchecking one series hide a different one.\n const next = effectiveHidden(hiddenSeries, seriesCount)\n if (next.has(index)) next.delete(index)\n else next.add(index)\n ;(onHiddenSeriesChange ?? setLocalHidden)(\n next.size >= seriesCount ? new Set() : next,\n )\n }","next","useCallback(\n (index: number) => {\n // Start from what the user sees, not the stored set: `data` can\n // shrink while the `series` names hold (the reset only watches the\n // names), and a stale index would otherwise count toward the guard\n // below — or make unchecking one series hide a different one.\n const next = effectiveHidden(hiddenSeries, seriesCount)\n if (next.has(index)) next.delete(index)\n else next.add(index)\n ;(onHiddenSeriesChange ?? setLocalHidden)(\n next.size >= seriesCount ? new Set() : next,\n )\n },\n [hiddenSeries, onHiddenSeriesChange, seriesCount],\n )","typeof maxItems === 'number' && Number.isFinite(maxItems) && maxItems > 0","hasCap ? grouped.slice(0, maxItems) : grouped","Box","listRefCallback","{\n ...styles.list,\n ...(isMulti && !stacked && styles.listMulti),\n ...(scrollMode && styles.listScroll),\n }","scrollMaxHeight !== undefined\n ? { maxHeight: scrollMaxHeight, overflowY: 'auto' }\n : undefined","visible.map((row) => {\n const selected = selectionSet.has(row.name)\n const dimmed = hasSelection && !selected\n const displayName = labelFormatter\n ? labelFormatter(row.name)\n : undefined\n // When the row is dimmed, every series' bar fill is replaced\n // with `dimColor`. Text + track are untouched. This is the\n // ONLY visual difference between selected and non-selected\n // rows — no row-level opacity, no `rowSelected` bg tint.\n const fill = (i: number): string =>\n dimmed ? dimColor : colorAt(i, row.rowColors[i])\n // Series hidden via the legend are dropped before reaching the\n // row components — they render whatever arrays they're given\n // with no notion of \"hidden\" themselves. `visibleIdx` is hoisted\n // above this loop (same for every row).\n if (isMulti) {\n if (stacked) {\n // Stacked-mode branch: one segmented bar per row. Toggling\n // `stacked` after the natural-height measurement does NOT\n // re-trigger measurement (see the `naturalHeight` comment\n // above) — accepted trade-off: stacked rows have a slightly\n // different height, so the scroll viewport may show extra\n // whitespace after the toggle. Visual only, not functional.\n // Stacked rows are per-row normalized — they don't take\n // the global `maxValue`. Each row's bar fills 100% of the\n // track and the segments split it by `value_i / sum`. The\n // numeric total in the header preserves the per-row\n // magnitude information.\n return (\n <CategoryRowStacked\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n values={visibleIdx.map((i) => row.values[i]!)}\n colors={visibleIdx.map((i) => fill(i))}\n seriesNames={visibleIdx.map(\n (i) => series?.[i]?.name ?? `Series ${i + 1}`,\n )}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n zeroPct={zeroPct}\n />\n )\n }\n return (\n <CategoryRowMulti\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n values={visibleIdx.map((i) => row.values[i]!)}\n colors={visibleIdx.map((i) => fill(i))}\n maxValue={maxValue}\n minValue={minValue}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n />\n )\n }\n return (\n <CategoryRowSingle\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n value={row.values[0] ?? 0}\n maxValue={maxValue}\n minValue={minValue}\n color={fill(0)}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n />\n )\n })","styles.root","(_, i) => i","i","isMulti && !stacked && styles.listMulti","scrollMode && styles.listScroll","hiddenCount > 0 && (\n <CategoryRowOther\n hiddenCount={hiddenCount}\n otherLabel={labels?.other}\n otherCountLabel={labels?.otherCount}\n showAllLabel={labels?.showAll}\n onShowAll={onShowAll}\n />\n )","other","otherCount","showAll","<Box\n ref={listRefCallback}\n sx={{\n ...styles.list,\n ...(isMulti && !stacked && styles.listMulti),\n ...(scrollMode && styles.listScroll),\n }}\n style={\n scrollMaxHeight !== undefined\n ? { maxHeight: scrollMaxHeight, overflowY: 'auto' }\n : undefined\n }\n >\n {visible.map((row) => {\n const selected = selectionSet.has(row.name)\n const dimmed = hasSelection && !selected\n const displayName = labelFormatter\n ? labelFormatter(row.name)\n : undefined\n // When the row is dimmed, every series' bar fill is replaced\n // with `dimColor`. Text + track are untouched. This is the\n // ONLY visual difference between selected and non-selected\n // rows — no row-level opacity, no `rowSelected` bg tint.\n const fill = (i: number): string =>\n dimmed ? dimColor : colorAt(i, row.rowColors[i])\n // Series hidden via the legend are dropped before reaching the\n // row components — they render whatever arrays they're given\n // with no notion of \"hidden\" themselves. `visibleIdx` is hoisted\n // above this loop (same for every row).\n if (isMulti) {\n if (stacked) {\n // Stacked-mode branch: one segmented bar per row. Toggling\n // `stacked` after the natural-height measurement does NOT\n // re-trigger measurement (see the `naturalHeight` comment\n // above) — accepted trade-off: stacked rows have a slightly\n // different height, so the scroll viewport may show extra\n // whitespace after the toggle. Visual only, not functional.\n // Stacked rows are per-row normalized — they don't take\n // the global `maxValue`. Each row's bar fills 100% of the\n // track and the segments split it by `value_i / sum`. The\n // numeric total in the header preserves the per-row\n // magnitude information.\n return (\n <CategoryRowStacked\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n values={visibleIdx.map((i) => row.values[i]!)}\n colors={visibleIdx.map((i) => fill(i))}\n seriesNames={visibleIdx.map(\n (i) => series?.[i]?.name ?? `Series ${i + 1}`,\n )}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n zeroPct={zeroPct}\n />\n )\n }\n return (\n <CategoryRowMulti\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n values={visibleIdx.map((i) => row.values[i]!)}\n colors={visibleIdx.map((i) => fill(i))}\n maxValue={maxValue}\n minValue={minValue}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n />\n )\n }\n return (\n <CategoryRowSingle\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n value={row.values[0] ?? 0}\n maxValue={maxValue}\n minValue={minValue}\n color={fill(0)}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n />\n )\n })}\n {hiddenCount > 0 && (\n <CategoryRowOther\n hiddenCount={hiddenCount}\n otherLabel={labels?.other}\n otherCountLabel={labels?.otherCount}\n showAllLabel={labels?.showAll}\n onShowAll={onShowAll}\n />\n )}\n </Box>","hasLegend && series && (\n <CategoryLegend\n // Trimmed: `series` is caller metadata and may outrun `data`,\n // and an entry with no data behind it has nothing to hide.\n series={series.slice(0, seriesCount)}\n colorAt={(i) => colorAt(i)}\n hiddenSeries={activeHidden}\n onToggle={toggleSeries}\n />\n )","<Box sx={styles.root}>\n <Box\n ref={listRefCallback}\n sx={{\n ...styles.list,\n ...(isMulti && !stacked && styles.listMulti),\n ...(scrollMode && styles.listScroll),\n }}\n style={\n scrollMaxHeight !== undefined\n ? { maxHeight: scrollMaxHeight, overflowY: 'auto' }\n : undefined\n }\n >\n {visible.map((row) => {\n const selected = selectionSet.has(row.name)\n const dimmed = hasSelection && !selected\n const displayName = labelFormatter\n ? labelFormatter(row.name)\n : undefined\n // When the row is dimmed, every series' bar fill is replaced\n // with `dimColor`. Text + track are untouched. This is the\n // ONLY visual difference between selected and non-selected\n // rows — no row-level opacity, no `rowSelected` bg tint.\n const fill = (i: number): string =>\n dimmed ? dimColor : colorAt(i, row.rowColors[i])\n // Series hidden via the legend are dropped before reaching the\n // row components — they render whatever arrays they're given\n // with no notion of \"hidden\" themselves. `visibleIdx` is hoisted\n // above this loop (same for every row).\n if (isMulti) {\n if (stacked) {\n // Stacked-mode branch: one segmented bar per row. Toggling\n // `stacked` after the natural-height measurement does NOT\n // re-trigger measurement (see the `naturalHeight` comment\n // above) — accepted trade-off: stacked rows have a slightly\n // different height, so the scroll viewport may show extra\n // whitespace after the toggle. Visual only, not functional.\n // Stacked rows are per-row normalized — they don't take\n // the global `maxValue`. Each row's bar fills 100% of the\n // track and the segments split it by `value_i / sum`. The\n // numeric total in the header preserves the per-row\n // magnitude information.\n return (\n <CategoryRowStacked\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n values={visibleIdx.map((i) => row.values[i]!)}\n colors={visibleIdx.map((i) => fill(i))}\n seriesNames={visibleIdx.map(\n (i) => series?.[i]?.name ?? `Series ${i + 1}`,\n )}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n zeroPct={zeroPct}\n />\n )\n }\n return (\n <CategoryRowMulti\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n values={visibleIdx.map((i) => row.values[i]!)}\n colors={visibleIdx.map((i) => fill(i))}\n maxValue={maxValue}\n minValue={minValue}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n />\n )\n }\n return (\n <CategoryRowSingle\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n value={row.values[0] ?? 0}\n maxValue={maxValue}\n minValue={minValue}\n color={fill(0)}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n />\n )\n })}\n {hiddenCount > 0 && (\n <CategoryRowOther\n hiddenCount={hiddenCount}\n otherLabel={labels?.other}\n otherCountLabel={labels?.otherCount}\n showAllLabel={labels?.showAll}\n onShowAll={onShowAll}\n />\n )}\n </Box>\n {hasLegend && series && (\n <CategoryLegend\n // Trimmed: `series` is caller metadata and may outrun `data`,\n // and an entry with no data behind it has nothing to hide.\n series={series.slice(0, seriesCount)}\n colorAt={(i) => colorAt(i)}\n hiddenSeries={activeHidden}\n onToggle={toggleSeries}\n />\n )}\n </Box>","_c","_temp","styles","{\n selection,\n onSelectionChange,\n series,\n maxItems,\n labels,\n onShowAll,\n maxOverride,\n minOverride,\n size,\n stacked,\n}: CategoryProps","useMemo(() => {\n const [min, max] = extentFromCategoryData(slice.rawData)\n if (relativeScale === 'total')\n return [min < 0 ? -100 : 0, max > 0 ? 100 : 0]\n if (relativeScale === 'max') return extentFromCategoryData(slice.data)\n return [min, max]\n }, [slice.rawData, slice.data, relativeScale])","extentFromCategoryData(slice.rawData)","rawData","[min < 0 ? -100 : 0, max > 0 ? 100 : 0]","min < 0 ? -100 : 0","max > 0 ? 100 : 0","extentFromCategoryData(slice.data)","data","[min, max]","categorySeriesKey(series)","(s) => s.categoryHiddenSeries","(indices: ReadonlySet<number>) =>\n getWidgetStore(id).setState({\n categoryHiddenSeries: { key: seriesKey, indices },\n } as Partial<CategoryWidgetState>)","useCallback(\n (indices: ReadonlySet<number>) =>\n getWidgetStore(id).setState({\n categoryHiddenSeries: { key: seriesKey, indices },\n } as Partial<CategoryWidgetState>),\n [id, seriesKey],\n )","<CategoryUI\n data={slice.data}\n formatter={slice.formatter}\n labelFormatter={slice.labelFormatter}\n selection={selection}\n onSelectionChange={onSelectionChange}\n series={series}\n maxItems={maxItems}\n labels={labels}\n onShowAll={onShowAll}\n maxOverride={effectiveMaxOverride}\n minOverride={effectiveMinOverride}\n size={size}\n stacked={stacked}\n hiddenSeries={hiddenSeries}\n onHiddenSeriesChange={onHiddenSeriesChange}\n />","formatter","labelFormatter","_c","{\n rows = 5,\n size = 'small',\n}: CategorySkeletonProps","rows = 5","size = 'small'","Array.from({ length: rows })","<Box sx={styles.root}>\n {Array.from({ length: rows }).map((_, i) => (\n <Box key={`row-${i}`} sx={styles.row}>\n <Box sx={styles.rowHeader}>\n <Skeleton variant='rectangular' sx={styles.name} />\n <Skeleton variant='rectangular' sx={styles.value} />\n </Box>\n <Skeleton\n variant='rectangular'\n sx={{\n height: BAR_HEIGHT[size],\n width: WIDTH_PATTERNS[i % WIDTH_PATTERNS.length],\n }}\n />\n </Box>\n ))}\n </Box>","_c","Tooltip$1","useWidgetShallow","useWidgetId","useWidget","getWidgetStore","selectRelativeScale","buildCsvDownloadItem","withPngItem","resolveThemeColor","styles$1","theme","CategoryBar","$","c","t1","t2","t3","t0","minValue","size","zeroTick","range","maxValue","leftPct","value","pct","t4","t5","t6","t7","t8","t9","t10","color","t11","Box","t12","t13","ZeroTickWrap","zeroPct","children","CategoryRowSingle","name","onToggle","handleKey","displayName","Typography","formatter","selected","CategoryRowMulti","min","values","colors","v","i","CategoryBarStacked","lefts","pos","neg","v_0","CategoryRowStacked","T0","T1","T2","scale","total","_temp$2","t14","t15","t16","t17","seriesNames","t18","t19","a","b","CategoryRowOther","otherLabel","otherCountLabel","showAllLabel","hiddenCount","countText","count","onShowAll","NO_HIDDEN_SERIES","categorySeriesKey","series","s","CategoryLegend","hiddenSeries","colorAt","hidden","DEFAULT_MAX_ITEMS","ROW_HEIGHT_PX","SCROLL_VIEWPORT_FALLBACK_ROWS","effectiveHidden","clamped","seriesCount","generateGroupedData","data","grouped","order","item","row","k","signedDomain","lo","hi","rows","minOverride","maxOverride","m","formatSigned","fmt","CategoryUI","hiddenSeriesProp","maxItems","stacked","useTheme","baseFmt","_temp$1","n_0","hasSelection","selection","selectionSet","onSelectionChange","cur","next","n_1","toggle","paletteColors","override","rowColor","seriesIndex","dimColor","scrollMode","measuredRef","useRef","naturalHeight","setNaturalHeight","useState","node","h","listRefCallback","localHidden","setLocalHidden","_temp2","seriesKey","prevSeriesKey","setPrevSeriesKey","onHiddenSeriesChange","next_0","index","toggleSeries","hasCap","visible","isMulti","hasLegend","activeHidden","t20","t21","t22","t23","labelFormatter","visibleIdx","_temp3","i_0","scrollMaxHeight","t24","t25","dimmed","fill","i_1","i_2","i_3","i_4","i_5","i_6","labels","t26","i_7","t27","n","categorySelector","extentFromCategoryData","max","d","Category","id","slice","relativeScale","naturalMin","naturalMax","effectiveMaxOverride","effectiveMinOverride","stored","_temp","styles","BAR_HEIGHT","WIDTH_PATTERNS","CategorySkeleton","Skeleton","createCategoryDownloadConfig","args","ctx","seriesName"],"sources":["../src/widgets/category/style.ts","../src/widgets/category/components/category-bar.tsx","../src/widgets/category/components/category-row-single.tsx","../src/widgets/category/components/category-row-multi.tsx","../src/widgets/category/components/category-bar-stacked.tsx","../src/widgets/category/components/category-row-stacked.tsx","../src/widgets/category/components/category-row-other.tsx","../src/widgets/category/helpers.ts","../src/widgets/category/components/category-legend.tsx","../src/widgets/category/category-ui.tsx","../src/widgets/category/category.tsx","../src/widgets/category/skeleton.tsx","../src/widgets/category/download.ts"],"sourcesContent":["import type { SxProps, Theme } from '@mui/material'\nimport type { CategorySize } from './types'\n\n// Size-keyed bar styles — scoped strictly to the bar primitive\n// (track + fill height & corner radius). `small` preserves the\n// historical look (4px-tall pill); `medium` renders a 12px-tall track\n// with a 2px corner radius. Every other token (gap, padding, hover,\n// transitions, label/value typography) is size-invariant.\nconst bar: Record<CategorySize, SxProps<Theme>> = {\n small: {\n position: 'relative',\n height: 4,\n bgcolor: 'action.hover',\n borderRadius: 1,\n overflow: 'hidden',\n transition: 'background-color 150ms ease',\n },\n medium: {\n position: 'relative',\n height: 12,\n bgcolor: 'action.hover',\n borderRadius: 0.25,\n overflow: 'hidden',\n transition: 'background-color 150ms ease',\n },\n}\n\nconst barFill: Record<CategorySize, SxProps<Theme>> = {\n small: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n borderRadius: 1,\n transition:\n 'left 300ms ease-in-out, width 300ms ease-in-out, background-color 150ms ease, filter 150ms ease',\n },\n medium: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n borderRadius: 0.25,\n transition:\n 'left 300ms ease-in-out, width 300ms ease-in-out, background-color 150ms ease, filter 150ms ease',\n },\n}\n\n// Square-cornered segment for the stacked bar. Adjacent segments meet\n// cleanly; the parent `bar[size]` track clips the outer ends via its\n// own borderRadius + overflow:hidden, so the stacked bar still has the\n// same rounded silhouette as a single `CategoryBar`.\nconst stackedSegment: Record<CategorySize, SxProps<Theme>> = {\n small: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n transition:\n 'left 300ms ease-in-out, width 300ms ease-in-out, background-color 150ms ease, filter 150ms ease',\n },\n medium: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n transition:\n 'left 300ms ease-in-out, width 300ms ease-in-out, background-color 150ms ease, filter 150ms ease',\n },\n}\n\nexport const styles = {\n // ── Top-level container ────────────────────────────────────────────\n root: {\n display: 'flex',\n flexDirection: 'column',\n position: 'relative',\n width: '100%',\n },\n // Flat list of category rows. A grid rather than a flex column so the\n // multi-series rows can share one value column through `subgrid`: with\n // every bar track the same width, the zero ticks line up across rows.\n list: {\n position: 'relative',\n display: 'grid',\n gridTemplateColumns: 'minmax(0, 1fr)',\n gap: 1,\n py: 0.5,\n },\n // Layered on `list` for unstacked multi-series: bars | values.\n listMulti: {\n gridTemplateColumns: 'minmax(0, 1fr) max-content',\n },\n // Zero tick for signed data: one per bar track, drawn outside the track's\n // `overflow: hidden` so it can stand 6px proud of it on both sides.\n // Tracks share a width (see `listMulti`), so the ticks line up by rows.\n barWrap: {\n position: 'relative',\n },\n // Multi-series rows draw one zero line through every series bar instead\n // of a tick per bar: this box is the row's bar column (grid area set\n // inline) and holds a `zeroTick`, so the line stays out of the label.\n multiZeroArea: {\n position: 'absolute',\n inset: 0,\n gridColumn: '1 / 2',\n pointerEvents: 'none',\n },\n zeroTick: {\n position: 'absolute',\n top: -6,\n bottom: -6,\n width: '1px',\n // Centred on the zero so both halves of the bar meet it evenly. No\n // z-index: it comes before the track in the DOM, so the bar paints over\n // it and only the overhang shows.\n transform: 'translateX(-50%)',\n bgcolor: 'text.disabled',\n pointerEvents: 'none',\n },\n // Layered on `list` only in scroll mode (`maxItems === 0`): right padding\n // so the bars + right-aligned values clear the scrollbar. Right side only\n // — the left edge stays at 0 so content stays aligned with the capped\n // view. `overflowY` + the dynamic `maxHeight` remain on the inline style.\n listScroll: {\n pr: 1,\n },\n\n // ── Single-series row ─────────────────────────────────────────────\n // v1 layout: label + value share a top flex row (justify-between); the\n // bar fills the row width below. Mirrors `rowMulti` for visual\n // consistency.\n rowSingle: {\n gridColumn: '1 / -1',\n display: 'flex',\n flexDirection: 'column',\n gap: 0.5,\n // py: 0.5,\n // px: 0.5,\n cursor: 'pointer',\n userSelect: 'none',\n transition: 'background-color 80ms ease',\n // Hovering anywhere in the row also brightens the bar fill, matching\n // what the bar got from `bar:hover` before. `data-bar-fill` is set\n // on `barFill` by <CategoryBar>; descendant selector reaches it\n // through `bar` in single rows and through `multiBarRow > bar` in\n // multi rows.\n '&:hover [data-bar-fill=\"true\"]': {\n filter: 'brightness(1.2)',\n },\n '&:focus-visible': {\n outline: '2px solid',\n outlineColor: 'primary.main',\n outlineOffset: -2,\n },\n },\n // Top row of `rowSingle`: label on the left, value on the right.\n rowHeader: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n gap: 1,\n },\n // ── Multi-series row ──────────────────────────────────────────────\n // Flex column: label fills the top row; bar+value pairs stack below.\n // Bars get full track width — better legibility for N>=2 series than a\n // shared grid would give.\n // Spans the list and adopts its columns, so every row's value column is\n // as wide as the widest value in the list.\n rowMulti: {\n position: 'relative',\n gridColumn: '1 / -1',\n display: 'grid',\n gridTemplateColumns: 'subgrid',\n rowGap: 0.5,\n // py: 0.5,\n // px: 0.5,\n cursor: 'pointer',\n userSelect: 'none',\n transition: 'background-color 80ms ease',\n // Row-hover brightens every bar fill inside the multi-row.\n '&:hover [data-bar-fill=\"true\"]': {\n filter: 'brightness(1.2)',\n },\n '&:focus-visible': {\n outline: '2px solid',\n outlineColor: 'primary.main',\n outlineOffset: -2,\n },\n },\n // ── Row labels / values ───────────────────────────────────────────\n // Matches v1: name uses medium-weight body2 in primary text color,\n // value uses regular body2 in secondary text color. Same font size,\n // visual hierarchy via weight + color.\n name: {\n overflow: 'hidden',\n whiteSpace: 'nowrap',\n textOverflow: 'ellipsis',\n color: 'text.primary',\n fontWeight: 'medium',\n },\n multiName: {\n gridColumn: '1 / -1',\n overflow: 'hidden',\n whiteSpace: 'nowrap',\n textOverflow: 'ellipsis',\n color: 'text.primary',\n fontWeight: 'medium',\n },\n value: {\n color: 'text.secondary',\n fontVariantNumeric: 'tabular-nums',\n // Right-align + minWidth keeps `rowMulti`'s per-bar value column\n // tidy (where the bar grows on the left and the number sits at a\n // fixed right edge). `rowSingle` uses `rowHeader`'s flex\n // justify-between to push the value to the trailing edge, so the\n // minWidth here is also harmless in that context.\n minWidth: 48,\n textAlign: 'right',\n alignSelf: 'center',\n },\n\n // ── Bar (track + fill) ────────────────────────────────────────────\n // Size-keyed maps live above the `styles` object; `<CategoryBar>` picks\n // the variant via its `size` prop. Hover brightening still fires from\n // the parent row (`&:hover [data-bar-fill]`), so it works regardless\n // of which size variant the bar uses.\n bar,\n barFill,\n stackedSegment,\n\n // ── Multi-series internals ────────────────────────────────────────\n // One [bar | value] flex row per series, inside `rowMulti`'s column.\n // `contents` hands the bar and the value to `rowMulti`'s subgrid cells.\n multiBarRow: {\n display: 'contents',\n },\n multiBarContainer: {\n alignSelf: 'center',\n },\n\n // ── Stacked-mode row ──────────────────────────────────────────────\n // Mirror `rowMulti`'s outer shape (click target + hover brightening +\n // focus ring) but stack three children vertically: header line, bar,\n // breakdown line.\n rowStacked: {\n gridColumn: '1 / -1',\n display: 'flex',\n flexDirection: 'column',\n gap: 0.5,\n cursor: 'pointer',\n userSelect: 'none',\n transition: 'background-color 80ms ease',\n '&:hover [data-bar-fill=\"true\"]': {\n filter: 'brightness(1.2)',\n },\n '&:focus-visible': {\n outline: '2px solid',\n outlineColor: 'primary.main',\n outlineOffset: -2,\n },\n },\n stackedHeader: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n gap: 1,\n },\n // Right-aligned total in the header. Tabular-nums keeps totals lined\n // up vertically across rows; minWidth reuses the per-series-value\n // column width so the right edge sits at the same spot as `rowMulti`'s\n // value column when both modes co-exist in the same widget card.\n stackedTotal: {\n color: 'text.primary',\n fontWeight: 'medium',\n fontVariantNumeric: 'tabular-nums',\n minWidth: 48,\n textAlign: 'right',\n },\n // Flex row of \"{seriesName}: {value}\" pairs. Wraps gracefully when\n // series names are long or there are many series.\n stackedBreakdown: {\n display: 'flex',\n flexWrap: 'wrap',\n columnGap: 2,\n rowGap: 0.25,\n },\n stackedBreakdownItem: {\n color: 'text.secondary',\n fontVariantNumeric: 'tabular-nums',\n },\n\n // ── Legend (sticky bottom) ────────────────────────────────────────\n legend: {\n display: 'flex',\n alignItems: 'center',\n gap: 2,\n flexWrap: 'wrap',\n pt: 2,\n mt: 2,\n position: 'sticky',\n bottom: 0,\n bgcolor: 'background.paper',\n borderTop: (theme: Theme) => `1px solid ${theme.palette.divider}`,\n },\n legendItem: {\n display: 'flex',\n alignItems: 'center',\n gap: 1,\n cursor: 'pointer',\n },\n legendItemHidden: {\n display: 'flex',\n alignItems: 'center',\n gap: 1,\n cursor: 'pointer',\n opacity: 0.5,\n },\n legendDot: {\n width: 14,\n height: 14,\n borderRadius: '50%',\n },\n legendLabel: {\n color: 'text.secondary',\n textTransform: 'uppercase',\n fontWeight: 'medium',\n },\n\n // ── \"Other\" overflow row ──────────────────────────────────────────\n // Left-aligned `Others <count>` — the label sits next to a muted count\n // (no parentheses). Only the label is the click target when interactive;\n // the count is always static text beside it.\n otherRow: {\n gridColumn: '1 / -1',\n display: 'flex',\n alignItems: 'center',\n gap: 1,\n py: 0.5,\n px: 0.5,\n },\n // Interactive affordances layered onto the \"Others\" label only (cursor,\n // hover tint, focus ring). A negative LEFT margin + matching padding keep\n // the hover pill breathing without shifting the label's text edge (it\n // stays aligned with the category rows above). No right margin, so the\n // row `gap` is preserved between the pill and the count.\n otherLabelButton: {\n cursor: 'pointer',\n userSelect: 'none',\n borderRadius: 1,\n px: 0.5,\n ml: -0.5,\n transition: 'background-color 80ms ease',\n '&:hover': {\n bgcolor: 'action.hover',\n },\n '&:focus-visible': {\n outline: '2px solid',\n outlineColor: 'primary.main',\n outlineOffset: 2,\n },\n },\n otherLabel: {\n color: 'text.primary',\n fontWeight: 'medium',\n },\n otherCount: {\n color: 'text.secondary',\n fontVariantNumeric: 'tabular-nums',\n },\n // `bar` / `barFill` are size-keyed (`Record<CategorySize, SxProps>`);\n // every other entry is a single `SxProps<Theme>`. The mixed shape means\n // we can't apply the file-wide `satisfies Record<string, SxProps<Theme>>`\n // constraint that the other widget style files use — consumers of\n // individual tokens still get full type-checking at the call site via\n // the typed `Box`/`Typography` `sx` prop.\n}\n","import type { CssColor } from '../../../components/types'\nimport type { ReactNode } from 'react'\nimport { Box } from '@mui/material'\nimport type { CategorySize } from '../types'\nimport { styles } from '../style'\n\nexport interface CategoryBarProps {\n /** Numeric value driving the fill width. */\n value: number\n /** Upper end of the domain. `maxValue === minValue` → empty bar (no division by zero). */\n maxValue: number\n /**\n * Lower end of the domain. `0` (default) keeps the positive-only bar\n * anchored at the left edge; a negative value moves the zero to\n * `-minValue / (maxValue - minValue)` and bars grow from there in either\n * direction, keeping the fill `color` whatever the sign.\n */\n minValue?: number\n /** Fill color, already resolved by `CategoryUI.colorAt`. */\n color: CssColor\n /**\n * Visual density. `'small'` (default) keeps the historical 4px pill;\n * `'medium'` renders a 12px-tall track with a 2px corner radius. Only\n * the bar track + fill change — every other token in the parent row is\n * size-invariant.\n */\n size?: CategorySize\n /**\n * Draw the zero tick when the domain is signed. `false` when the parent\n * draws its own zero line (multi-series rows span it across every bar).\n */\n zeroTick?: boolean\n}\n\n/**\n * Single horizontal bar (track + proportional fill). Pure presentation.\n * Selection / dimming live at the row level; this component is unaware of\n * both — bars stay vivid regardless of selection state (per the v2\n * \"row-bg + row-dim, never muted bar\" rule).\n */\nexport function CategoryBar({\n value,\n maxValue,\n minValue = 0,\n color,\n size = 'small',\n zeroTick = true,\n}: CategoryBarProps) {\n const range = maxValue - minValue\n const leftPct =\n range > 0 ? ((Math.min(0, value) - minValue) / range) * 100 : 0\n const pct = range > 0 ? (Math.abs(value) / range) * 100 : 0\n const hasZeroTick = zeroTick && minValue < 0 && maxValue > 0\n // Width + color rendered as inline `style` (not `sx`) to avoid\n // generating a new emotion class for every distinct value/color pair\n // — also makes the rendered DOM testable without computed-style math.\n return (\n <ZeroTickWrap zeroPct={hasZeroTick ? (-minValue / range) * 100 : undefined}>\n <Box sx={styles.bar[size]} data-size={size}>\n <Box\n sx={styles.barFill[size]}\n style={{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: color,\n // The end that grows out of the zero is square; only the far\n // end keeps the rounded corners.\n ...(value < 0\n ? { borderTopRightRadius: 0, borderBottomRightRadius: 0 }\n : { borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }),\n }}\n // Tag the fill so an ancestor row's `:hover` selector can target\n // it (`&:hover [data-bar-fill]`) — the brightening affordance now\n // fires from anywhere in the row, not only the bar track.\n data-bar-fill='true'\n />\n </Box>\n </ZeroTickWrap>\n )\n}\n\n/**\n * Wraps a bar track and, when `zeroPct` is set, marks the zero with a tick\n * that overhangs the track. Positive-only, negative-only and all-zero bars\n * pass `undefined` and get no tick.\n */\nexport function ZeroTickWrap({\n zeroPct,\n children,\n}: {\n zeroPct: number | undefined\n children: ReactNode\n}) {\n if (zeroPct === undefined) return children\n return (\n <Box sx={styles.barWrap}>\n <Box\n aria-hidden\n data-zero-tick='true'\n sx={styles.zeroTick}\n style={{ left: `${zeroPct}%` }}\n />\n {children}\n </Box>\n )\n}\n","import type { CssColor } from '../../../components/types'\nimport type { KeyboardEvent } from 'react'\nimport { Box, Typography } from '@mui/material'\nimport { CategoryBar } from './category-bar'\nimport type { CategoryKey, CategorySize } from '../types'\nimport { styles } from '../style'\n\nexport interface CategoryRowSingleProps {\n /** Raw category key — what gets emitted to `onToggle` regardless of formatter. */\n name: CategoryKey\n /** Display label (post-`labelFormatter`). Falls back to `name` when omitted. */\n displayName?: string | number\n value: number\n maxValue: number\n /** Lower end of the shared domain (`0` for positive-only data). */\n minValue?: number\n color: CssColor\n formatter: (n: number) => string\n selected: boolean\n onToggle: (name: CategoryKey) => void\n /** Bar visual density. Forwarded to {@link CategoryBar}. */\n size?: CategorySize\n}\n\n/**\n * Single-series category row: `[label] [bar] [value]` grid. Click /\n * Enter / Space toggles `name` in/out of the selection set via\n * `onToggle`. Selection key is always the raw `name` — `displayName` is\n * presentation only.\n *\n * Visual selection / dim signal lives ENTIRELY in the bar color\n * (`color` prop) — computed upstream by `CategoryUI` (real palette\n * color when selected or no-selection; `theme.palette.action.disabled`\n * when a selection exists and this row isn't in it). The row itself\n * has no bg tint, no opacity rule.\n */\nexport function CategoryRowSingle({\n name,\n displayName,\n value,\n maxValue,\n minValue,\n color,\n formatter,\n selected,\n onToggle,\n size,\n}: CategoryRowSingleProps) {\n const handleKey = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle(name)\n }\n }\n return (\n <Box\n role='button'\n aria-pressed={selected}\n tabIndex={0}\n onClick={() => onToggle(name)}\n onKeyDown={handleKey}\n sx={styles.rowSingle}\n >\n <Box sx={styles.rowHeader}>\n <Typography variant='body2' sx={styles.name}>\n {displayName ?? name}\n </Typography>\n <Typography variant='body2' sx={styles.value}>\n {formatter(value)}\n </Typography>\n </Box>\n <CategoryBar\n value={value}\n maxValue={maxValue}\n minValue={minValue}\n color={color}\n size={size}\n />\n </Box>\n )\n}\n","import type { CssColor } from '../../../components/types'\nimport type { KeyboardEvent } from 'react'\nimport { Box, Typography } from '@mui/material'\nimport { CategoryBar } from './category-bar'\nimport type { CategoryKey, CategorySize } from '../types'\nimport { styles } from '../style'\n\nexport interface CategoryRowMultiProps {\n /** Raw category key — what gets emitted to `onToggle`. */\n name: CategoryKey\n /** Display label (post-`labelFormatter`). Falls back to `name`. */\n displayName?: string | number\n /** One value per series. `0` is rendered as an empty bar (track only). */\n values: readonly number[]\n /** One color per series (palette + per-series overrides resolved upstream). */\n colors: readonly CssColor[]\n /** Shared max across all series + rows (or `maxOverride`). */\n maxValue: number\n /** Lower end of the shared domain (`0` for positive-only data). */\n minValue?: number\n formatter: (n: number) => string\n selected: boolean\n onToggle: (name: CategoryKey) => void\n /** Bar visual density. Forwarded to every {@link CategoryBar} in the row. */\n size?: CategorySize\n}\n\n/**\n * Multi-series category row: label spans the top; bar+value pairs stack\n * vertically below, one per series. Each bar uses the full row width\n * track (no shared grid) for legibility at N>=2 series. Click anywhere\n * in the row (label OR any bar/value) toggles selection by `name`.\n *\n * Visual selection / dim signal lives ENTIRELY in the bar colors\n * (`colors` prop) — every series in a dimmed row receives\n * `theme.palette.action.disabled`; selected / unselected-no-selection\n * rows receive their real palette colors. Computed upstream by\n * `CategoryUI`. The row itself has no bg tint, no opacity rule.\n */\nexport function CategoryRowMulti({\n name,\n displayName,\n values,\n colors,\n maxValue,\n minValue,\n formatter,\n selected,\n onToggle,\n size,\n}: CategoryRowMultiProps) {\n const min = minValue ?? 0\n const handleKey = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle(name)\n }\n }\n return (\n <Box\n role='button'\n aria-pressed={selected}\n tabIndex={0}\n onClick={() => onToggle(name)}\n onKeyDown={handleKey}\n sx={styles.rowMulti}\n >\n <Typography variant='body2' sx={styles.multiName}>\n {displayName ?? name}\n </Typography>\n {min < 0 && maxValue > 0 && (\n <Box\n aria-hidden\n sx={styles.multiZeroArea}\n // Row 1 is the label; the bars fill the next `values.length` rows.\n // Placed before the bars so their tracks paint over the line.\n style={{ gridRow: `2 / span ${values.length}` }}\n >\n <Box\n data-zero-tick='true'\n sx={styles.zeroTick}\n style={{ left: `${(-min / (maxValue - min)) * 100}%` }}\n />\n </Box>\n )}\n {values.map((v, i) => (\n // Multi-bar rows iterate per series — the bar belongs to series `i`,\n // and the row is keyed on the parent's `name`, so a composite of\n // `name` + the series index is stable across reorders.\n <Box key={`${name}-series-${i}`} sx={styles.multiBarRow}>\n <Box sx={styles.multiBarContainer}>\n <CategoryBar\n value={v}\n maxValue={maxValue}\n minValue={minValue}\n color={colors[i] ?? ''}\n size={size}\n zeroTick={false}\n />\n </Box>\n <Typography variant='body2' sx={styles.value}>\n {formatter(v)}\n </Typography>\n </Box>\n ))}\n </Box>\n )\n}\n","import type { CssColor } from '../../../components/types'\nimport { Box } from '@mui/material'\nimport type { CategorySize } from '../types'\nimport { styles } from '../style'\nimport { ZeroTickWrap } from './category-bar'\n\nexport interface CategoryBarStackedProps {\n /** One value per series. Zero values render no segment (visually + in the DOM). */\n values: readonly number[]\n /** One color per series. Resolved upstream by `CategoryUI.colorAt`. */\n colors: readonly CssColor[]\n /**\n * Shared denominator across all rows + series (or `maxOverride`). Same\n * value `CategoryBar` uses — segments scale identically to single-bar rows\n * so cross-row comparison is preserved.\n */\n maxValue: number\n /**\n * Where the zero sits, as a percentage of the track. `0` (default) is the\n * positive-only layout. Positive segments stack rightwards from it and\n * negative ones leftwards, both scaled by `maxValue` against the full\n * track width.\n */\n zeroPct?: number\n /** Visual density. Forwarded to the track styling; segments inherit height. */\n size?: CategorySize\n}\n\n/**\n * Stacked horizontal bar: one rounded track containing N square segments\n * placed side-by-side. Each segment's width is `value_i / maxValue * 100%`\n * and its left offset is the cumulative sum of preceding values. Square\n * interior edges + the parent track's `overflow: hidden` + rounded radius\n * mean the outer ends of the stacked bar match a single `CategoryBar` while\n * interior segment boundaries meet cleanly.\n *\n * Pure presentation — selection / dimming is resolved upstream by the row\n * via the `colors` prop. Each segment carries `data-bar-fill='true'` so the\n * existing row-hover brightening selector still fires on every segment.\n */\nexport function CategoryBarStacked({\n values,\n colors,\n maxValue,\n zeroPct = 0,\n size = 'small',\n}: CategoryBarStackedProps) {\n // Left edges pre-computed up front so the render map function stays pure\n // (no mutable closure variable — keeps the React Compiler immutability\n // check happy). Each sign keeps its own running sum away from the zero.\n const lefts: number[] = []\n {\n let pos = 0\n let neg = 0\n for (const v of values) {\n if (v < 0) neg -= v\n lefts.push(v < 0 ? -neg : pos)\n if (v > 0) pos += v\n }\n }\n return (\n <ZeroTickWrap zeroPct={zeroPct > 0 && zeroPct < 100 ? zeroPct : undefined}>\n <Box sx={styles.bar[size]} data-size={size}>\n {values.map((v, i) => {\n if (v === 0 || maxValue <= 0) return null\n const leftPct = zeroPct + (lefts[i]! / maxValue) * 100\n const pct = (Math.abs(v) / maxValue) * 100\n return (\n <Box\n // Segments are keyed by per-series colour (the only stable\n // distinguishing field on this side) plus the segment index for\n // when callers reuse palette colours across series.\n key={`seg-${colors[i] ?? 'none'}-${i}`}\n sx={styles.stackedSegment[size]}\n style={{\n left: `${leftPct}%`,\n width: `${pct}%`,\n backgroundColor: colors[i] ?? '',\n }}\n data-bar-fill='true'\n />\n )\n })}\n </Box>\n </ZeroTickWrap>\n )\n}\n","import type { CssColor } from '../../../components/types'\nimport type { KeyboardEvent } from 'react'\nimport { Box, Typography } from '@mui/material'\nimport { CategoryBarStacked } from './category-bar-stacked'\nimport type { CategoryKey, CategorySize } from '../types'\nimport { styles } from '../style'\n\nexport interface CategoryRowStackedProps {\n /** Raw category key — what gets emitted to `onToggle`. */\n name: CategoryKey\n /** Display label (post-`labelFormatter`). Falls back to `name`. */\n displayName?: string | number\n /** One value per series. Zero values still appear in the breakdown row. */\n values: readonly number[]\n /** One color per series (palette + per-series overrides resolved upstream). */\n colors: readonly CssColor[]\n /**\n * Series names for the breakdown row, one per visible series, already\n * resolved by the caller (unnamed series fall back to `Series N` using\n * their ORIGINAL series index — this component has no notion of which\n * series were hidden, so it must not recompute a positional fallback).\n */\n seriesNames: readonly string[]\n formatter: (n: number) => string\n selected: boolean\n onToggle: (name: CategoryKey) => void\n /** Bar visual density. Forwarded to the inner {@link CategoryBarStacked}. */\n size?: CategorySize\n /**\n * Where the list's zero sits (% of the track), shared by every row so the\n * zero lines up: `0` positive-only, `50` signed, `100` negative-only.\n */\n zeroPct?: number\n}\n\n/**\n * Stacked-mode category row: label + total share a header line; a single\n * segmented bar sits below; an inline breakdown lists each series with its\n * formatted value. Click anywhere in the row toggles selection by `name`.\n *\n * Per-row normalization: the stacked bar always fills 100% of the track,\n * with each segment sized as `value_i / sum(values)`. This is intentionally\n * different from `CategoryRowMulti` (which scales bars against the global\n * `maxValue` for cross-row comparison) — in stacked mode the comparable\n * axis is the proportion of each series within the row, not the row's\n * total relative to other rows. The total numeric value is still shown in\n * the header so the per-row magnitude isn't lost.\n *\n * Signed data diverges from `zeroPct`: positives stack rightwards and\n * negatives leftwards, and whichever side is longer reaches its edge of the\n * track. The header total is the signed sum.\n *\n * Visual selection / dim signal lives entirely in the segment colors\n * (`colors` prop) — every series in a dimmed row receives\n * `theme.palette.action.disabled`. The bar visually collapses to a single\n * grey shape when dimmed, but the breakdown text stays at full color so the\n * per-series values remain legible.\n */\nexport function CategoryRowStacked({\n name,\n displayName,\n values,\n colors,\n seriesNames,\n formatter,\n selected,\n onToggle,\n size,\n zeroPct = 0,\n}: CategoryRowStackedProps) {\n const handleKey = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle(name)\n }\n }\n const total = values.reduce((a, b) => a + b, 0)\n let pos = 0\n let neg = 0\n for (const v of values) {\n if (v > 0) pos += v\n else neg -= v\n }\n // Magnitude that would fill the whole track, picked so the longer side\n // just reaches its edge: `pos` spans `100 - zeroPct`, `neg` spans `zeroPct`.\n const scale = Math.max(\n zeroPct < 100 ? (pos * 100) / (100 - zeroPct) : 0,\n zeroPct > 0 ? (neg * 100) / zeroPct : 0,\n )\n return (\n <Box\n role='button'\n aria-pressed={selected}\n tabIndex={0}\n onClick={() => onToggle(name)}\n onKeyDown={handleKey}\n sx={styles.rowStacked}\n >\n <Box sx={styles.stackedHeader}>\n <Typography variant='body2' sx={styles.multiName}>\n {displayName ?? name}\n </Typography>\n <Typography variant='body2' sx={styles.stackedTotal}>\n {formatter(total)}\n </Typography>\n </Box>\n <CategoryBarStacked\n values={values}\n colors={colors}\n maxValue={scale}\n zeroPct={zeroPct}\n size={size}\n />\n <Box sx={styles.stackedBreakdown}>\n {values.map((v, i) => (\n // Series name is the stable identity for the breakdown line.\n <Typography\n key={`breakdown-${seriesNames[i]}`}\n variant='body2'\n sx={styles.stackedBreakdownItem}\n >\n {`${seriesNames[i]}: ${formatter(v)}`}\n </Typography>\n ))}\n </Box>\n </Box>\n )\n}\n","import type { KeyboardEvent } from 'react'\nimport { Box, Typography } from '@mui/material'\nimport { Tooltip } from '../../../components'\nimport { styles } from '../style'\n\nexport interface CategoryRowOtherProps {\n hiddenCount: number\n /** Label for the overflow row. Defaults to `'Others'`. */\n otherLabel?: string\n /** Count text with `{count}` placeholder. Defaults to `'{count}'`. */\n otherCountLabel?: string\n /**\n * When provided, the row becomes a button: clicking it (or Enter/Space)\n * fires `onShowAll`, expanding the widget to reveal every category. When\n * omitted the row renders as a static, non-interactive summary.\n */\n onShowAll?: () => void\n /** Tooltip shown over the button form. Defaults to `'Show all'`. */\n showAllLabel?: string\n}\n\n/**\n * Overflow summary row rendered after the last visible category row when\n * `data.length > maxItems`. Shows `Others <count>` (count muted, no\n * parentheses).\n *\n * When `onShowAll` is supplied the row is an interactive button — clicking\n * it (or Enter/Space) expands the widget to show all categories, mirroring\n * the Searcher's full-list view. Without `onShowAll` it stays a static,\n * non-interactive summary (backward-compatible default).\n */\nexport function CategoryRowOther({\n hiddenCount,\n otherLabel = 'Others',\n otherCountLabel = '{count}',\n onShowAll,\n showAllLabel = 'Show all',\n}: CategoryRowOtherProps) {\n const countText = otherCountLabel.replace('{count}', String(hiddenCount))\n\n const count = (\n <Typography variant='body2' sx={styles.otherCount}>\n {countText}\n </Typography>\n )\n\n if (!onShowAll) {\n return (\n <Box sx={styles.otherRow}>\n <Typography variant='body2' sx={styles.otherLabel}>\n {otherLabel}\n </Typography>\n {count}\n </Box>\n )\n }\n\n const handleKey = (e: KeyboardEvent<HTMLSpanElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onShowAll()\n }\n }\n\n // Only the label is the click target; the count stays static beside it.\n return (\n <Box sx={styles.otherRow}>\n <Tooltip title={showAllLabel}>\n <Typography\n variant='body2'\n component='span'\n role='button'\n tabIndex={0}\n onClick={onShowAll}\n onKeyDown={handleKey}\n sx={{ ...styles.otherLabel, ...styles.otherLabelButton }}\n >\n {otherLabel}\n </Typography>\n </Tooltip>\n {count}\n </Box>\n )\n}\n","import type { CategorySeriesConfig } from './types'\n\n/** Shared empty set: \"no series hidden\" without a new identity per render. */\nexport const NO_HIDDEN_SERIES: ReadonlySet<number> = new Set()\n\n/**\n * Identifies a series breakdown by its names. Hidden-series indices are only\n * valid for the breakdown they were made for, and both the store copy (in\n * `Category`) and the local one (in `CategoryUI`) are tagged with this key —\n * so they must compute it the same way.\n */\nexport function categorySeriesKey(\n series: readonly CategorySeriesConfig[] | undefined,\n): string {\n return JSON.stringify(series?.map((s) => s.name) ?? [])\n}\n","import type { CssColor } from '../../../components/types'\nimport { Box, Typography } from '@mui/material'\nimport type { CategorySeriesConfig } from '../types'\nimport { styles } from '../style'\nimport { NO_HIDDEN_SERIES } from '../helpers'\n\nexport interface CategoryLegendProps {\n /** Series metadata. Empty array → renders nothing. */\n series: readonly CategorySeriesConfig[]\n /**\n * Per-series colour resolver. Receives the series index and returns the\n * palette colour (or `undefined` to fall back to the MUI default). The\n * legend derives its dot colour from this on the fly — the caller does\n * NOT pre-compute a `colors` array (avoids passing two views of the\n * same source).\n */\n colorAt: (index: number) => CssColor | undefined\n /**\n * Series indices currently hidden from the chart. Renders dimmed.\n * Optional — defaults to none hidden. Added after this component's first\n * release; kept optional (not required) so it stays a non-breaking\n * addition for any direct consumer of `CategoryLegend` (a public\n * subpath export) that predates the hide feature.\n */\n hiddenSeries?: ReadonlySet<number>\n /**\n * Fires with the series index when its legend item is clicked. Optional\n * — omitting it renders a legend with no hide affordance (same\n * reasoning as `hiddenSeries`).\n */\n onToggle?: (index: number) => void\n}\n\n/**\n * Sticky color legend rendered below a multi-series Category list.\n * Per-series `color` overrides the matching `colorAt(i)` result. Clicking an\n * item toggles that series' visibility in the rows above (mirrors ECharts'\n * native legend click-to-hide).\n */\nexport function CategoryLegend({\n series,\n colorAt,\n hiddenSeries = NO_HIDDEN_SERIES,\n onToggle,\n}: CategoryLegendProps) {\n if (series.length === 0) return null\n return (\n <Box sx={styles.legend}>\n {series.map((s, i) => {\n const hidden = hiddenSeries.has(i)\n return (\n <Box\n key={s.name}\n // `checkbox`/`aria-checked`, not `button`/`aria-pressed` — this\n // toggles a persistent visibility state (closer to a checkbox)\n // AND keeps it out of `getByRole('button')` queries that count\n // category ROWS throughout this widget's test suite.\n role='checkbox'\n aria-checked={!hidden}\n tabIndex={0}\n onClick={() => onToggle?.(i)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onToggle?.(i)\n }\n }}\n sx={hidden ? styles.legendItemHidden : styles.legendItem}\n >\n <Box\n sx={styles.legendDot}\n style={{ backgroundColor: colorAt(i) }}\n />\n <Typography variant='caption' sx={styles.legendLabel}>\n {s.name}\n </Typography>\n </Box>\n )\n })}\n </Box>\n )\n}\n","import { useCallback, useMemo, useRef, useState } from 'react'\nimport { Box, useTheme } from '@mui/material'\nimport { CategoryRowSingle } from './components/category-row-single'\nimport { CategoryRowMulti } from './components/category-row-multi'\nimport { CategoryRowStacked } from './components/category-row-stacked'\nimport { CategoryRowOther } from './components/category-row-other'\nimport { CategoryLegend } from './components/category-legend'\nimport type {\n CategoryKey,\n CategoryLabels,\n CategorySeriesConfig,\n CategorySize,\n CategoryWidgetData,\n} from './types'\nimport { styles } from './style'\nimport { categorySeriesKey } from './helpers'\nimport { resolveThemeColor } from '../utils'\n\nconst DEFAULT_MAX_ITEMS = 20\n\n// Fallback viewport height used in scroll mode ONLY when no natural\n// measurement is available yet — i.e., the consumer mounts directly into\n// `maxItems === 0` (e.g., SearcherToggle pre-enabled), so the list has\n// never rendered in capped form for us to measure.\n//\n// small: label(~20) + gap(4) + bar(4) + list-gap(8) ≈ 36\n// medium: label(~20) + gap(4) + bar(12) + list-gap(8) ≈ 44\nconst ROW_HEIGHT_PX: Record<CategorySize, number> = { small: 36, medium: 44 }\nconst SCROLL_VIEWPORT_FALLBACK_ROWS = 8\n\nexport interface CategoryUIProps {\n data: CategoryWidgetData\n /** Currently-selected category names (destination-owned). */\n selection?: readonly CategoryKey[]\n /** Fires with the next selection set when a row is clicked. */\n onSelectionChange?: (next: readonly CategoryKey[]) => void\n /** Number formatter applied to each row's value. */\n formatter?: (value: number) => string\n /**\n * Display-only transform for category names. Selection callbacks always\n * receive the raw `item.name` regardless of this formatter.\n */\n labelFormatter?: (value: string | number) => string | number\n /**\n * Per-series metadata. Enables the legend (for multi-series) and\n * overrides palette colors per series index. Length need not match\n * `data.length`; mismatches degrade gracefully (extras ignored, gaps\n * fall back to palette).\n */\n series?: readonly CategorySeriesConfig[]\n /**\n * Caps the number of visible category rows.\n *\n * - `undefined` (omitted) — caps at {@link DEFAULT_MAX_ITEMS} (20).\n * Surplus rows fold into a single \"Others <count>\" footer row.\n * `undefined` cannot mean \"no cap\" because it's consumed by the\n * default-parameter syntax — pass `null` instead.\n * - positive finite N — same as the default but with a custom cap.\n * - `0` — **no cap WITH scroll**: every row renders inside a\n * fixed-height viewport that scrolls internally. The viewport's\n * max-height is the list's natural `clientHeight` measured on the\n * first render where the list was NOT in scroll mode (i.e., the\n * capped view) — frozen for the component's lifetime. If the\n * consumer mounts directly into scroll mode and we never see a\n * capped render, the viewport falls back to `8 × ROW_HEIGHT_PX`.\n * Composers flip into this mode while the user is searching\n * (`maxItems = searcherOpen ? 0 : userMaxItems`) so the widget\n * card height stays stable.\n * - `null` / `Infinity` / negative — **no cap, no scroll**: render\n * every row; the list grows with content. Matches v1's \"undefined\"\n * behavior. Use `null` as the canonical explicit form.\n */\n maxItems?: number | null\n /** Labels for the \"Other\" overflow row. `{count}` placeholder is replaced. */\n labels?: CategoryLabels\n /**\n * When provided, the \"Other\" overflow row becomes a button — clicking it\n * fires `onShowAll`, which a composer typically wires to expand the widget\n * (drop the row cap) so every category becomes reachable. When omitted the\n * overflow row stays a static summary.\n */\n onShowAll?: () => void\n /**\n * Manual override for the top of the bar scale. With signed data it is a\n * magnitude: the scale stays symmetric around the centred zero.\n */\n maxOverride?: number\n /**\n * Manual override for the bottom of the bar scale, the negative mirror of\n * `maxOverride`. Ignored unless negative.\n */\n minOverride?: number\n /**\n * Visual density of the bar primitive. `'small'` (default) keeps the\n * historical 4px-tall pill; `'medium'` switches to a 12px-tall track\n * with a 2px corner radius. Only the bar track + fill change.\n */\n size?: CategorySize\n /**\n * Multi-series stacked mode. When `true` and `data.length > 1`, each\n * category renders as a single segmented bar (one segment per series)\n * with a `formatter(sum)` total in the header and a per-series\n * breakdown line below. Composers typically thread this from\n * `useTransformEnabled(id, 'stack-toggle')`.\n *\n * No-op for single-series data — same convention as the legend.\n */\n stacked?: boolean\n /**\n * Series indices hidden via the legend. Leave both this and\n * `onHiddenSeriesChange` unset and CategoryUI keeps the set itself; pass\n * them to own it — `Category` does, so the card and the fullscreen copy\n * share it through the widget store.\n */\n hiddenSeries?: ReadonlySet<number>\n onHiddenSeriesChange?: (next: ReadonlySet<number>) => void\n}\n\ninterface GroupedRow {\n name: CategoryKey\n values: number[]\n /** Per-series per-row color override (first non-undefined wins). */\n rowColors: (string | undefined)[]\n}\n\n/**\n * The hidden set as the chart applies it: indices past the current series\n * count are ignored, and an all-hidden result falls back to all visible.\n * The stored set is never rewritten from here, so hidden indices survive a\n * data shrink and come back if the data regrows.\n */\nfunction effectiveHidden(\n hidden: ReadonlySet<number>,\n seriesCount: number,\n): Set<number> {\n const clamped = new Set([...hidden].filter((i) => i < seriesCount))\n return clamped.size >= seriesCount ? new Set<number>() : clamped\n}\n\n/**\n * Group items by `name` across series so each unique name produces one\n * row containing N values (zero-filled where a series has no entry for\n * that name). First-seen order across series is preserved.\n *\n * Ported from v1\n * (`packages/react-ui/src/widgets/category/category-ui.tsx:137-166`)\n * with readonly typing + per-row color capture.\n */\nfunction generateGroupedData(data: CategoryWidgetData): GroupedRow[] {\n if (!data || data.length === 0) return []\n const seriesCount = Math.max(data.length, 1)\n const grouped = new Map<CategoryKey, GroupedRow>()\n const order: CategoryKey[] = []\n for (let s = 0; s < data.length; s++) {\n const series = data[s] ?? []\n for (const item of series) {\n let row = grouped.get(item.name)\n if (!row) {\n row = {\n name: item.name,\n values: new Array<number>(seriesCount).fill(0),\n rowColors: new Array<string | undefined>(seriesCount).fill(undefined),\n }\n grouped.set(item.name, row)\n order.push(item.name)\n }\n row.values[s] = item.value\n row.rowColors[s] = item.color\n }\n }\n return order.map((k) => grouped.get(k)!)\n}\n\n/**\n * Bar domain `[lo, hi]`, always containing zero. Positive-only data keeps\n * the zero at the left edge and negative-only at the right; mixed data is\n * made symmetric so the zero sits in the middle of the track.\n */\nfunction signedDomain(\n rows: readonly GroupedRow[],\n minOverride?: number,\n maxOverride?: number,\n): [lo: number, hi: number] {\n let lo = 0\n let hi = 0\n for (const row of rows) {\n for (const v of row.values) {\n if (v < lo) lo = v\n if (v > hi) hi = v\n }\n }\n if (typeof minOverride === 'number' && minOverride < 0) lo = minOverride\n if (typeof maxOverride === 'number' && maxOverride > 0) hi = maxOverride\n if (lo < 0 && hi > 0) {\n const m = Math.max(-lo, hi)\n return [-m, m]\n }\n return [lo, hi]\n}\n\n/**\n * The widget owns the sign: `formatter` only ever sees `|v|`, so a formatter\n * that prefixes a unit reads `−$5`, not `$-5`, and one that emits its own\n * `-` cannot double it. U+2212 is as wide as a tabular digit.\n */\nfunction formatSigned(fmt: (n: number) => string, v: number): string {\n return (v < 0 ? '\\u2212' : '') + fmt(Math.abs(v))\n}\n\n/**\n * Pure presentational component for the Category widget. Renders\n * single-series rows (`data.length === 1`) as a compact grid or\n * multi-series rows (`data.length > 1`) as stacked-bar groups, capped at\n * `maxItems` with an overflow summary, and an optional sticky color\n * legend when `series` metadata is supplied.\n *\n * Selection is destination-owned: clicking a row fires\n * `onSelectionChange(next)` with the toggled set; the consumer keeps the\n * list in their own store. `aria-pressed` + Enter/Space keyboard\n * activation give full a11y parity with the mouse path.\n *\n * Returns `null` when there's nothing to render — `Widget.State` is the\n * empty-state authority in the canonical compositor.\n */\nexport function CategoryUI({\n data,\n selection,\n onSelectionChange,\n formatter,\n labelFormatter,\n series,\n maxItems = DEFAULT_MAX_ITEMS,\n labels,\n onShowAll,\n maxOverride,\n minOverride,\n size = 'small',\n stacked = false,\n hiddenSeries: hiddenSeriesProp,\n onHiddenSeriesChange,\n}: CategoryUIProps) {\n const theme = useTheme()\n const baseFmt = formatter ?? ((n: number) => String(n))\n const fmt = (n: number) => formatSigned(baseFmt, n)\n\n const hasSelection = !!selection && selection.length > 0\n const selectionSet = useMemo(\n () => new Set<CategoryKey>(selection ?? []),\n [selection],\n )\n\n const toggle = useCallback(\n (name: CategoryKey) => {\n if (!onSelectionChange) return\n const cur = selection ?? []\n const next = cur.includes(name)\n ? cur.filter((n) => n !== name)\n : [...cur, name]\n onSelectionChange(next)\n },\n [onSelectionChange, selection],\n )\n\n // Palette: same convention as Bar/Pie/Histogram/Scatterplot/Timeseries v2.\n const paletteColors = useMemo<readonly string[]>(\n () => [\n theme.palette.secondary.main,\n ...Object.values(\n (theme.palette as { qualitative?: { bold?: Record<string, string> } })\n .qualitative?.bold ?? {},\n ),\n ],\n [theme],\n )\n\n // Resolved here, not at the paint site: the bars and the legend dot use\n // inline `style` to keep emotion from minting a class per value/colour\n // pair, and inline style has no theme resolver behind it — a caller's\n // `'primary.main'` would reach the DOM verbatim and paint nothing.\n const colorAt = useCallback(\n (seriesIndex: number, rowColor?: string): string => {\n const override = rowColor ?? series?.[seriesIndex]?.color\n if (override) return resolveThemeColor(theme, override) ?? override\n return (\n paletteColors[seriesIndex % paletteColors.length] ??\n theme.palette.secondary.main\n )\n },\n [series, paletteColors, theme],\n )\n\n // Dim color for non-selected rows when a selection exists. Swapped in\n // at the bar-fill level only — text + track stay at their real colors.\n // Theme-driven so it survives palette swaps.\n const dimColor = theme.palette.action.disabled\n\n const grouped = useMemo(() => generateGroupedData(data), [data])\n\n const [minValue, maxValue] = useMemo(\n () => signedDomain(grouped, minOverride, maxOverride),\n [grouped, minOverride, maxOverride],\n )\n // Where the zero sits on the track, handed to stacked rows so their zero\n // matches the other rows'. Positive-only data keeps it at the left edge.\n const zeroPct =\n maxValue > minValue ? (-minValue / (maxValue - minValue)) * 100 : 0\n\n // Three modes:\n // * positive finite N (default `DEFAULT_MAX_ITEMS = 20` when prop\n // omitted): cap at N rows; surplus folds into \"Others <count>\".\n // * `0`: no cap WITH scroll — every row renders, the list locks to a\n // fixed viewport and scrolls internally. Composers use this to\n // bypass pagination while the user is searching:\n // `maxItems = searcherOpen ? 0 : userMaxItems`\n // * `null` / `Infinity` / negative: no cap, no scroll — every row\n // renders and the list grows with content. `null` is the canonical\n // explicit form (`undefined` is consumed by the default param above).\n const scrollMode = maxItems === 0\n\n // Natural-height measurement. In scroll mode (`maxItems === 0`,\n // set by the composer when the SearcherToggle is open) every row\n // renders inside a scrollable viewport. The viewport's max-height\n // must match the pre-overflow content area — i.e., whatever the\n // list was rendering BEFORE the composer dropped the cap.\n //\n // We measure the list's `clientHeight` exactly once, the first time\n // it attaches while NOT in scroll mode (otherwise we'd capture the\n // wrong layout). Once set, `naturalHeight` is the lock anchor\n // forever — matches v1's frozen-at-mount semantics, just with a\n // real DOM measurement instead of a hard-coded constant.\n //\n // Implemented via a callback ref instead of `useLayoutEffect` so\n // setState fires during commit, not inside an effect — satisfies\n // the `react-hooks/set-state-in-effect` rule. React invokes\n // callback refs on attach (and re-invokes them when the callback\n // identity changes, which it does when `scrollMode` flips), so\n // toggling the searcher off after a scroll-mode mount triggers the\n // first measurement at that point.\n //\n // Fallback: if the consumer never lands a non-scroll render\n // (e.g., searcher pre-enabled and never closed), `naturalHeight`\n // stays `null` → viewport falls back to `8 × ROW_HEIGHT_PX`.\n const measuredRef = useRef(false)\n const [naturalHeight, setNaturalHeight] = useState<number | null>(null)\n const listRefCallback = useCallback(\n (node: HTMLDivElement | null) => {\n if (!node) return\n if (measuredRef.current) return\n if (scrollMode) return\n const h = node.clientHeight\n if (h > 0) {\n measuredRef.current = true\n setNaturalHeight(h)\n }\n },\n [scrollMode],\n )\n\n // Which series are hidden via a legend click. Local UI-only state — not\n // synced to `selection`/`onSelectionChange` (that's row-level filter\n // selection, unrelated). Never allowed to reach \"every series hidden\":\n // toggling the last visible one instead resets to \"all visible\" rather\n // than leaving the widget blank.\n const [localHidden, setLocalHidden] = useState<ReadonlySet<number>>(\n () => new Set(),\n )\n const hiddenSeries = hiddenSeriesProp ?? localHidden\n // A reload with a different series breakdown invalidates hidden indices —\n // index 2 may now point at an entirely different series. Reset rather\n // than keep hiding whatever now occupies a stale index. Adjusted directly\n // during render (React's documented \"adjusting state when a prop changes\"\n // pattern) instead of in a `useEffect`, which would commit the stale\n // hidden-set for one extra frame before the effect fires. Compared by a\n // content-derived key, not the raw `series` reference — an inline\n // `series={[...]}` literal (no useMemo) gets a new reference on every\n // unrelated parent re-render, which would otherwise reset hiddenSeries\n // right after the user hides one.\n const seriesKey = categorySeriesKey(series)\n const [prevSeriesKey, setPrevSeriesKey] = useState(seriesKey)\n if (seriesKey !== prevSeriesKey) {\n setPrevSeriesKey(seriesKey)\n setLocalHidden(new Set())\n }\n // Real number of data series — NOT `series?.length`, which is legend\n // metadata and may legitimately be shorter than the actual series count\n // (unnamed trailing series fall back to `Series N`). Using the metadata\n // length here would trip the \"never fully hidden\" guard early whenever\n // `series` is shorter than `data`.\n const seriesCount = Math.max(data.length, 1)\n const toggleSeries = useCallback(\n (index: number) => {\n // Start from what the user sees, not the stored set: `data` can\n // shrink while the `series` names hold (the reset only watches the\n // names), and a stale index would otherwise count toward the guard\n // below — or make unchecking one series hide a different one.\n const next = effectiveHidden(hiddenSeries, seriesCount)\n if (next.has(index)) next.delete(index)\n else next.add(index)\n ;(onHiddenSeriesChange ?? setLocalHidden)(\n next.size >= seriesCount ? new Set() : next,\n )\n },\n [hiddenSeries, onHiddenSeriesChange, seriesCount],\n )\n\n if (grouped.length === 0) return null\n\n const hasCap =\n typeof maxItems === 'number' && Number.isFinite(maxItems) && maxItems > 0\n const visible = hasCap ? grouped.slice(0, maxItems) : grouped\n const hiddenCount = hasCap ? grouped.length - visible.length : 0\n\n const isMulti = data.length > 1\n const hasLegend = isMulti && !!series && series.length > 0\n // Invariant across every row in this render — only depends on\n // `hiddenSeries`/`seriesCount`, not on any particular row's data — so\n // it's computed once here instead of per row inside the render loop.\n const activeHidden = effectiveHidden(hiddenSeries, seriesCount)\n const visibleIdx = Array.from({ length: seriesCount }, (_, i) => i).filter(\n (i) => !activeHidden.has(i),\n )\n\n // Fallback viewport height matches the documented \"8 × ROW_HEIGHT_PX\"\n // (see the constants at the top of the file). Earlier code multiplied\n // this by `seriesCount`, making a 3-series widget render 3× the\n // intended viewport on the first frame before `naturalHeight` was\n // measured — a visible layout jump.\n const scrollMaxHeight = scrollMode\n ? (naturalHeight ?? ROW_HEIGHT_PX[size] * SCROLL_VIEWPORT_FALLBACK_ROWS)\n : undefined\n\n return (\n <Box sx={styles.root}>\n <Box\n ref={listRefCallback}\n sx={{\n ...styles.list,\n ...(isMulti && !stacked && styles.listMulti),\n ...(scrollMode && styles.listScroll),\n }}\n style={\n scrollMaxHeight !== undefined\n ? { maxHeight: scrollMaxHeight, overflowY: 'auto' }\n : undefined\n }\n >\n {visible.map((row) => {\n const selected = selectionSet.has(row.name)\n const dimmed = hasSelection && !selected\n const displayName = labelFormatter\n ? labelFormatter(row.name)\n : undefined\n // When the row is dimmed, every series' bar fill is replaced\n // with `dimColor`. Text + track are untouched. This is the\n // ONLY visual difference between selected and non-selected\n // rows — no row-level opacity, no `rowSelected` bg tint.\n const fill = (i: number): string =>\n dimmed ? dimColor : colorAt(i, row.rowColors[i])\n // Series hidden via the legend are dropped before reaching the\n // row components — they render whatever arrays they're given\n // with no notion of \"hidden\" themselves. `visibleIdx` is hoisted\n // above this loop (same for every row).\n if (isMulti) {\n if (stacked) {\n // Stacked-mode branch: one segmented bar per row. Toggling\n // `stacked` after the natural-height measurement does NOT\n // re-trigger measurement (see the `naturalHeight` comment\n // above) — accepted trade-off: stacked rows have a slightly\n // different height, so the scroll viewport may show extra\n // whitespace after the toggle. Visual only, not functional.\n // Stacked rows are per-row normalized — they don't take\n // the global `maxValue`. Each row's bar fills 100% of the\n // track and the segments split it by `value_i / sum`. The\n // numeric total in the header preserves the per-row\n // magnitude information.\n return (\n <CategoryRowStacked\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n values={visibleIdx.map((i) => row.values[i]!)}\n colors={visibleIdx.map((i) => fill(i))}\n seriesNames={visibleIdx.map(\n (i) => series?.[i]?.name ?? `Series ${i + 1}`,\n )}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n zeroPct={zeroPct}\n />\n )\n }\n return (\n <CategoryRowMulti\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n values={visibleIdx.map((i) => row.values[i]!)}\n colors={visibleIdx.map((i) => fill(i))}\n maxValue={maxValue}\n minValue={minValue}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n />\n )\n }\n return (\n <CategoryRowSingle\n key={String(row.name)}\n name={row.name}\n displayName={displayName}\n value={row.values[0] ?? 0}\n maxValue={maxValue}\n minValue={minValue}\n color={fill(0)}\n formatter={fmt}\n selected={selected}\n onToggle={toggle}\n size={size}\n />\n )\n })}\n {hiddenCount > 0 && (\n <CategoryRowOther\n hiddenCount={hiddenCount}\n otherLabel={labels?.other}\n otherCountLabel={labels?.otherCount}\n showAllLabel={labels?.showAll}\n onShowAll={onShowAll}\n />\n )}\n </Box>\n {hasLegend && series && (\n <CategoryLegend\n // Trimmed: `series` is caller metadata and may outrun `data`,\n // and an entry with no data behind it has nothing to hide.\n series={series.slice(0, seriesCount)}\n colorAt={(i) => colorAt(i)}\n hiddenSeries={activeHidden}\n onToggle={toggleSeries}\n />\n )}\n </Box>\n )\n}\n","import { useCallback, useMemo } from 'react'\nimport {\n getWidgetStore,\n useWidget,\n useWidgetId,\n useWidgetShallow,\n type WidgetState,\n} from '../stores'\nimport { selectRelativeScale } from '../actions/relative-data/scale'\nimport { CategoryUI } from './category-ui'\nimport { categorySeriesKey, NO_HIDDEN_SERIES } from './helpers'\nimport type {\n CategoryKey,\n CategoryLabels,\n CategorySeriesConfig,\n CategorySize,\n CategoryWidgetData,\n} from './types'\n\ninterface CategorySlice {\n data: CategoryWidgetData\n rawData: CategoryWidgetData | undefined\n formatter?: (value: number) => string\n labelFormatter?: (value: string | number) => string | number\n}\n\nconst categorySelector = (s: WidgetState): CategorySlice => ({\n data: (s.data ?? []) as CategoryWidgetData,\n rawData: (s.rawData ?? undefined) as CategoryWidgetData | undefined,\n formatter: s.formatter,\n labelFormatter: s.labelFormatter,\n})\n\n/**\n * Legend-hidden series, kept on the widget store so every mounted Category\n * of the widget — the card and its fullscreen copy — shows the same set.\n * Tagged with the series breakdown it was made for: after a reload with\n * different series the indices point elsewhere, so a stale set reads as\n * empty instead of being reset with a write.\n */\ninterface CategoryWidgetState extends WidgetState {\n categoryHiddenSeries?: { key: string; indices: ReadonlySet<number> }\n}\n\n/** `[min, max]` of every value, each clamped to include zero. */\nfunction extentFromCategoryData(\n d: CategoryWidgetData | undefined,\n): [min: number, max: number] {\n let min = 0\n let max = 0\n for (const series of d ?? []) {\n for (const item of series) {\n if (item.value < min) min = item.value\n if (item.value > max) max = item.value\n }\n }\n return [min, max]\n}\n\nexport interface CategoryProps {\n /** Currently-selected category names. Destination-owned. */\n selection?: readonly CategoryKey[]\n /** Fires when a row is clicked. Consumer updates the destination's store. */\n onSelectionChange?: (next: readonly CategoryKey[]) => void\n /** Per-series metadata. Enables the legend + overrides palette per index. */\n series?: readonly CategorySeriesConfig[]\n /**\n * Cap visible rows; overflow folds into an \"Others <count>\" row. Default 20\n * (when omitted). Pass `0` to swap the cap for a scrollable viewport\n * (composers use this when the user opens the SearcherToggle —\n * `maxItems = searcherOpen ? 0 : userMaxItems`). Pass `null` to disable\n * the cap entirely without adding a scroll viewport.\n */\n maxItems?: number | null\n /** Labels for the \"Other\" overflow row. */\n labels?: CategoryLabels\n /**\n * When provided, the \"Other\" overflow row becomes a button that fires this\n * callback. Composers wire it to expand the widget (e.g. flip the\n * `show-all` flag) so every category is shown. Forwarded to\n * {@link CategoryUI}.\n */\n onShowAll?: () => void\n /**\n * Manual override for the bar-width denominator. When omitted, the\n * bridge auto-fills from the widget store's `rawData` so bar widths\n * stay coherent across data transforms (e.g., the Searcher filtering\n * rows in and out — bars don't rescale just because the larger rows\n * got hidden). Pass an explicit number to fix the denominator\n * regardless of the data. Ignored while `RelativeData` is on, whose\n * `scale` decides the denominator instead.\n */\n maxOverride?: number\n /**\n * Bottom of the bar scale for negative values, the mirror of\n * `maxOverride`. Auto-filled from `rawData` the same way, so the zero\n * does not move when the Searcher hides every negative row.\n */\n minOverride?: number\n /**\n * Visual density of the bar primitive. `'small'` (default) keeps the\n * historical 4px-tall pill; `'medium'` switches to a 12px-tall track\n * with a 2px corner radius. Forwarded as-is to {@link CategoryUI}.\n */\n size?: CategorySize\n /**\n * Multi-series stacked mode. Forwarded as-is to {@link CategoryUI}.\n * Composers typically wire this from\n * `useTransformEnabled(id, 'stack-toggle')`. No-op for single-series.\n */\n stacked?: boolean\n}\n\n/**\n * Stateful Category bridge — reads `data`, `formatter`, and\n * `labelFormatter` (post-pipeline) from the per-widget store and\n * forwards them to the pure {@link CategoryUI}. `selection` and\n * `onSelectionChange` follow the destination-owned principle: the\n * consumer keeps the list in their own store and passes it through.\n */\nexport function Category({\n selection,\n onSelectionChange,\n series,\n maxItems,\n labels,\n onShowAll,\n maxOverride,\n minOverride,\n size,\n stacked,\n}: CategoryProps) {\n const id = useWidgetId()\n const slice = useWidgetShallow(id, categorySelector)\n // Auto-fill both ends of the scale from `rawData` so bar widths and the\n // zero stay coherent across data transforms (e.g., the Searcher filtering\n // rows in/out). Consumer's explicit value wins, but each end only applies\n // when the data reaches past zero on that side, so positive-only data\n // keeps its zero at the left edge, negative-only at the right. With no\n // data on a side we pass nothing and CategoryUI falls back to the live\n // `data`.\n // With RelativeData on, the values are percentages: `'total'` ends the\n // scale at ±100, `'max'` at the largest share so the top row fills.\n const relativeScale = useWidget(id, selectRelativeScale)\n const [naturalMin, naturalMax] = useMemo(() => {\n const [min, max] = extentFromCategoryData(slice.rawData)\n if (relativeScale === 'total')\n return [min < 0 ? -100 : 0, max > 0 ? 100 : 0]\n if (relativeScale === 'max') return extentFromCategoryData(slice.data)\n return [min, max]\n }, [slice.rawData, slice.data, relativeScale])\n // The overrides are in absolute units, so they sit out while relative\n // values are on.\n const effectiveMaxOverride =\n naturalMax > 0\n ? ((relativeScale ? undefined : maxOverride) ?? naturalMax)\n : undefined\n const effectiveMinOverride =\n naturalMin < 0\n ? ((relativeScale ? undefined : minOverride) ?? naturalMin)\n : undefined\n\n const seriesKey = categorySeriesKey(series)\n const stored = useWidget<\n CategoryWidgetState['categoryHiddenSeries'],\n CategoryWidgetState\n >(id, (s) => s.categoryHiddenSeries)\n const hiddenSeries =\n stored?.key === seriesKey ? stored.indices : NO_HIDDEN_SERIES\n const onHiddenSeriesChange = useCallback(\n (indices: ReadonlySet<number>) =>\n getWidgetStore(id).setState({\n categoryHiddenSeries: { key: seriesKey, indices },\n } as Partial<CategoryWidgetState>),\n [id, seriesKey],\n )\n return (\n <CategoryUI\n data={slice.data}\n formatter={slice.formatter}\n labelFormatter={slice.labelFormatter}\n selection={selection}\n onSelectionChange={onSelectionChange}\n series={series}\n maxItems={maxItems}\n labels={labels}\n onShowAll={onShowAll}\n maxOverride={effectiveMaxOverride}\n minOverride={effectiveMinOverride}\n size={size}\n stacked={stacked}\n hiddenSeries={hiddenSeries}\n onHiddenSeriesChange={onHiddenSeriesChange}\n />\n )\n}\n","import { Box, Skeleton } from '@mui/material'\nimport type { SxProps, Theme } from '@mui/material'\nimport type { CategorySize } from './types'\n\nconst styles = {\n root: {\n display: 'flex',\n flexDirection: 'column',\n gap: 1,\n py: 0.5,\n },\n // Mirror the real row layout: label + value on top, bar below.\n row: {\n display: 'flex',\n flexDirection: 'column',\n gap: 0.5,\n },\n rowHeader: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n gap: 1,\n },\n // Small fixed-width shimmer pills — matches the visual density of the\n // real label / value cells without claiming the full row width.\n name: { height: 14, width: 80 },\n value: { height: 14, width: 48 },\n} satisfies Record<string, SxProps<Theme>>\n\n// Bar shimmer height matches the live bar track for each size variant so\n// the skeleton doesn't visibly resize the row on data settle.\nconst BAR_HEIGHT: Record<CategorySize, number> = {\n small: 4,\n medium: 12,\n}\n\n// Varied widths give the bar shimmer a more realistic, less uniform feel.\n// Cycles when `rows > WIDTH_PATTERNS.length`.\nconst WIDTH_PATTERNS = ['95%', '80%', '55%', '75%', '60%']\n\nexport interface CategorySkeletonProps {\n rows?: number\n /**\n * Visual density. Mirrors the live widget's `size` prop so the loading\n * shimmer's bar height matches what's about to render — no row jump\n * on data settle.\n */\n size?: CategorySize\n}\n\nexport function CategorySkeleton({\n rows = 5,\n size = 'small',\n}: CategorySkeletonProps) {\n return (\n <Box sx={styles.root}>\n {Array.from({ length: rows }).map((_, i) => (\n <Box key={`row-${i}`} sx={styles.row}>\n <Box sx={styles.rowHeader}>\n <Skeleton variant='rectangular' sx={styles.name} />\n <Skeleton variant='rectangular' sx={styles.value} />\n </Box>\n <Skeleton\n variant='rectangular'\n sx={{\n height: BAR_HEIGHT[size],\n width: WIDTH_PATTERNS[i % WIDTH_PATTERNS.length],\n }}\n />\n </Box>\n ))}\n </Box>\n )\n}\n","import {\n buildCsvDownloadItem,\n withPngItem,\n type DownloadItem,\n type WidgetDownloadArgs,\n} from '../actions/download'\nimport type { CategoryWidgetData } from './types'\n\n/**\n * Download menu items for the Category widget. Always includes a CSV item\n * with `series, name, value` columns (one row per item). When `getCaptureEl`\n * is given, prepends a PNG item that rasterises the captured element via\n * `html2canvas`.\n */\nexport function createCategoryDownloadConfig(\n args: WidgetDownloadArgs<CategoryWidgetData> & {\n seriesNames?: readonly string[]\n },\n): DownloadItem[] {\n return withPngItem(\n args,\n buildCsvDownloadItem({\n filename: args.filename,\n getRows: (ctx) => {\n const data = args.getData(ctx)\n const rows: unknown[][] = [['series', 'name', 'value']]\n for (const [i, series] of data.entries()) {\n const seriesName = args.seriesNames?.[i] ?? `series_${i + 1}`\n for (const item of series) {\n rows.push([seriesName, item.name, item.value])\n }\n }\n return rows\n },\n }),\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/widgets/actions/relative-data/scale.ts
|
|
2
|
+
var e = "relative-data";
|
|
3
|
+
function t(t) {
|
|
4
|
+
let n = t.transformStates[e];
|
|
5
|
+
return n?.enabled === !0 ? n.scale ?? "max" : null;
|
|
6
|
+
}
|
|
7
|
+
var n = (e) => {
|
|
8
|
+
if (typeof e != "object" || !e) return e;
|
|
9
|
+
let t = e, n = t.yAxis;
|
|
10
|
+
return typeof n != "object" || !n || Array.isArray(n) ? e : {
|
|
11
|
+
...t,
|
|
12
|
+
yAxis: {
|
|
13
|
+
...n,
|
|
14
|
+
min: -100,
|
|
15
|
+
max: 100
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
function r(e) {
|
|
20
|
+
let { min: t, max: n } = e;
|
|
21
|
+
return {
|
|
22
|
+
min: typeof t == "number" ? t : void 0,
|
|
23
|
+
max: typeof n == "number" ? n : void 0
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { n as i, r as n, t as r, e as t };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=scale-Bhx8HKEB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scale-Bhx8HKEB.js","names":["RELATIVE_DATA_ID","selectRelativeScale","slot","s","toRelativeTotalAxis","option","cfg","yAxis","fixedAxisBounds","min","max"],"sources":["../src/widgets/actions/relative-data/scale.ts"],"sourcesContent":["import type { WidgetState } from '../../stores/types'\n\n/**\n * How far a bar reaches while `RelativeData` is on. The value is always\n * the share of the series total; the scale only moves the end of the\n * value axis: `'max'` ends it at the largest share so the top item fills\n * the bar, `'total'` ends it at 100%.\n */\nexport type RelativeScale = 'max' | 'total'\n\nexport const RELATIVE_DATA_ID = 'relative-data'\n\n/**\n * The scale the value axis should use right now, or `null` when relative\n * values are off. For widgets that size bars themselves (Category); the\n * ECharts widgets get it through the config transform below.\n */\nexport function selectRelativeScale(s: WidgetState): RelativeScale | null {\n const slot = s.transformStates[RELATIVE_DATA_ID]\n if (slot?.enabled !== true) return null\n return (slot.scale as RelativeScale | undefined) ?? 'max'\n}\n\n/**\n * Config transform for `'total'`: pins the value axis to ±100. The widget\n * mergers read numeric `yAxis.min` / `yAxis.max` back through\n * {@link fixedAxisBounds} and apply each only on the side the data reaches.\n */\nexport const toRelativeTotalAxis = (option: unknown): unknown => {\n if (option == null || typeof option !== 'object') return option\n const cfg = option as Record<string, unknown>\n const yAxis = cfg.yAxis\n if (yAxis == null || typeof yAxis !== 'object' || Array.isArray(yAxis))\n return option\n return { ...cfg, yAxis: { ...yAxis, min: -100, max: 100 } }\n}\n\n/** Numeric bounds a config transform set on the structural `yAxis`. */\nexport function fixedAxisBounds(yAxis: object): {\n min?: number\n max?: number\n} {\n const { min, max } = yAxis as { min?: unknown; max?: unknown }\n return {\n min: typeof min === 'number' ? min : undefined,\n max: typeof max === 'number' ? max : undefined,\n }\n}\n"],"mappings":";AAUA,IAAaA,IAAmB;AAOhC,SAAgBC,EAAoB,GAAsC;CACxE,IAAMC,IAAOC,EAAE,gBAAgBH;CAE/B,OADIE,GAAM,YAAY,KACdA,EAAK,SAAuC,QADjB;AAErC;AAOA,IAAaE,KAAuB,MAA6B;CAC/D,IAAsB,OAAOC,KAAW,aAApCA,GAA8C,OAAOA;CACzD,IAAMC,IAAMD,GACNE,IAAQD,EAAI;CAGlB,OAFqB,OAAOC,KAAU,aAAlCA,KAA8C,MAAM,QAAQA,CAAK,IAC5DF,IACF;EAAE,GAAGC;EAAK,OAAO;GAAE,GAAGC;GAAO,KAAK;GAAM,KAAK;EAAI;CAAE;AAC5D;AAGA,SAAgBC,EAAgB,GAG9B;CACA,IAAM,EAAE,QAAK,WAAQD;CACrB,OAAO;EACL,KAAK,OAAOE,KAAQ,WAAWA,IAAM,KAAA;EACrC,KAAK,OAAOC,KAAQ,WAAWA,IAAM,KAAA;CACvC;AACF"}
|
|
@@ -3,7 +3,7 @@ export { StackToggle, addStack, DEFAULT_STACK_TOGGLE_LABELS, type StackTogglePro
|
|
|
3
3
|
export { ShowAllToggle, setShowAll, SHOW_ALL_ID, DEFAULT_SHOW_ALL_LABELS, type ShowAllToggleProps, type ShowAllLabels, } from './show-all/index.js';
|
|
4
4
|
export { ZoomToggle, addZoom, createAddZoom, ZOOM_LAYOUT, DEFAULT_ZOOM_TOGGLE_LABELS, type ZoomToggleProps, type ZoomToggleLabels, } from './zoom-toggle/index.js';
|
|
5
5
|
export { BrushToggle, addBrush, DEFAULT_BRUSH_TOGGLE_LABELS, type BrushToggleProps, type BrushToggleLabels, } from './brush-toggle/index.js';
|
|
6
|
-
export { RelativeData, toRelativeData, createPercentFormatter, DEFAULT_RELATIVE_DATA_LABELS, type RelativeDataProps, type RelativeDataLabels, } from './relative-data/index.js';
|
|
6
|
+
export { RelativeData, toRelativeData, createPercentFormatter, DEFAULT_RELATIVE_DATA_LABELS, type RelativeDataProps, type RelativeDataLabels, type RelativeScale, } from './relative-data/index.js';
|
|
7
7
|
export { Download, downloadToCSV, downloadDOMToPNG, toCsvString, triggerLinkDownload, buildPngDownloadItem, buildCsvDownloadItem, withPngItem, CSVIcon, PNGIcon, MarkdownIcon, DEFAULT_DOWNLOAD_LABELS, DOWNLOAD_ITEM_IDS, type DownloadProps, type DownloadItem, type DownloadContext, type DownloadItemId, type DownloadHandle, type DownloadDOMToPNGOptions, type BuildPngDownloadItemArgs, type BuildCsvDownloadItemArgs, type WidgetDownloadArgs, type DownloadLabels, } from './download/index.js';
|
|
8
8
|
export { FullScreen, DEFAULT_FULLSCREEN_LABELS, type FullScreenTriggerProps, type FullScreenSlotProps, type FullScreenLabels, type FullScreenWidgetState, } from './fullscreen/index.js';
|
|
9
9
|
export { LockSelection, filterByLockedItems, DEFAULT_LOCK_SELECTION_LABELS, type LockSelectionProps, type LockSelectionKey, type LockSelectionLabels, } from './lock-selection/index.js';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { RelativeData, type RelativeDataProps } from './relative-data.js';
|
|
2
2
|
export { toRelativeData, createPercentFormatter } from './transforms.js';
|
|
3
|
+
export type { RelativeScale } from './scale.js';
|
|
3
4
|
export { DEFAULT_RELATIVE_DATA_LABELS, type RelativeDataLabels } from './labels.js';
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { ComponentType } from 'react';
|
|
2
2
|
import { SvgIconProps } from '@mui/material';
|
|
3
|
+
import { RelativeScale } from './scale.js';
|
|
3
4
|
import { RelativeDataLabels } from './labels.js';
|
|
4
5
|
export interface RelativeDataProps {
|
|
5
6
|
initialEnabled?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Initial bar scale: `'max'` (the largest item fills its bar) or
|
|
9
|
+
* `'total'` (bars end at 100%). Values are the share of the total
|
|
10
|
+
* either way. Only seeds the store — the menu, or a write to
|
|
11
|
+
* `transformStates['relative-data'].scale`, changes it afterwards.
|
|
12
|
+
*/
|
|
13
|
+
scale?: RelativeScale;
|
|
6
14
|
/**
|
|
7
15
|
* BCP-47 locale tag forwarded to `Intl.NumberFormat`. Defaults to the
|
|
8
16
|
* runtime locale.
|
|
@@ -25,7 +33,8 @@ export interface RelativeDataProps {
|
|
|
25
33
|
transform?: (data: unknown) => unknown;
|
|
26
34
|
}
|
|
27
35
|
/**
|
|
28
|
-
* Toggle between absolute and relative (percentage) values
|
|
36
|
+
* Toggle between absolute and relative (percentage) values, as a split
|
|
37
|
+
* button: the toggle plus an arrow that opens the scale menu.
|
|
29
38
|
*
|
|
30
39
|
* - **Data side**: a single data transform ({@link toRelativeData}) registered
|
|
31
40
|
* through `useSingleTransform`. When disabled, the pipeline skips it and the
|
|
@@ -35,5 +44,10 @@ export interface RelativeDataProps {
|
|
|
35
44
|
* reads `state.rawFormatter` (the consumer-supplied original, always live)
|
|
36
45
|
* and writes it back to `formatter`. No "original" snapshot is captured —
|
|
37
46
|
* `rawFormatter` is the original.
|
|
47
|
+
* - **Scale**: lives in `transformStates['relative-data'].scale` next to
|
|
48
|
+
* `enabled`, so duplicate mounts (Toolbox inline + overflow) and
|
|
49
|
+
* `keepAlive` remounts agree on it. It never changes the values; under
|
|
50
|
+
* `'total'` a config transform pins the ECharts value axis to 100%, and
|
|
51
|
+
* Category reads it through `selectRelativeScale`.
|
|
38
52
|
*/
|
|
39
|
-
export declare function RelativeData({ initialEnabled, locale, labels, icon: Icon, iconProps, transform, }: RelativeDataProps): import('react').JSX.Element;
|
|
53
|
+
export declare function RelativeData({ initialEnabled, scale: initialScale, locale, labels, icon: Icon, iconProps, transform, }: RelativeDataProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { WidgetState } from '../../stores/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* How far a bar reaches while `RelativeData` is on. The value is always
|
|
4
|
+
* the share of the series total; the scale only moves the end of the
|
|
5
|
+
* value axis: `'max'` ends it at the largest share so the top item fills
|
|
6
|
+
* the bar, `'total'` ends it at 100%.
|
|
7
|
+
*/
|
|
8
|
+
export type RelativeScale = 'max' | 'total';
|
|
9
|
+
export declare const RELATIVE_DATA_ID = "relative-data";
|
|
10
|
+
/**
|
|
11
|
+
* The scale the value axis should use right now, or `null` when relative
|
|
12
|
+
* values are off. For widgets that size bars themselves (Category); the
|
|
13
|
+
* ECharts widgets get it through the config transform below.
|
|
14
|
+
*/
|
|
15
|
+
export declare function selectRelativeScale(s: WidgetState): RelativeScale | null;
|
|
16
|
+
/**
|
|
17
|
+
* Config transform for `'total'`: pins the value axis to ±100. The widget
|
|
18
|
+
* mergers read numeric `yAxis.min` / `yAxis.max` back through
|
|
19
|
+
* {@link fixedAxisBounds} and apply each only on the side the data reaches.
|
|
20
|
+
*/
|
|
21
|
+
export declare const toRelativeTotalAxis: (option: unknown) => unknown;
|
|
22
|
+
/** Numeric bounds a config transform set on the structural `yAxis`. */
|
|
23
|
+
export declare function fixedAxisBounds(yAxis: object): {
|
|
24
|
+
min?: number;
|
|
25
|
+
max?: number;
|
|
26
|
+
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Theme } from '@mui/material';
|
|
2
2
|
export declare const styles: {
|
|
3
|
+
group: {
|
|
4
|
+
display: "inline-flex";
|
|
5
|
+
alignItems: "center";
|
|
6
|
+
};
|
|
3
7
|
toggle: {
|
|
4
8
|
p: number;
|
|
5
9
|
'& .MuiSvgIcon-root': {
|
|
@@ -9,4 +13,12 @@ export declare const styles: {
|
|
|
9
13
|
toggleActive: {
|
|
10
14
|
background: (theme: Theme) => string;
|
|
11
15
|
};
|
|
16
|
+
arrow: {
|
|
17
|
+
p: number;
|
|
18
|
+
width: number;
|
|
19
|
+
borderRadius: number;
|
|
20
|
+
'& .MuiSvgIcon-root': {
|
|
21
|
+
fontSize: number;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
12
24
|
};
|
|
@@ -29,7 +29,8 @@ export interface CategoryProps {
|
|
|
29
29
|
* stay coherent across data transforms (e.g., the Searcher filtering
|
|
30
30
|
* rows in and out — bars don't rescale just because the larger rows
|
|
31
31
|
* got hidden). Pass an explicit number to fix the denominator
|
|
32
|
-
* regardless of the data.
|
|
32
|
+
* regardless of the data. Ignored while `RelativeData` is on, whose
|
|
33
|
+
* `scale` decides the denominator instead.
|
|
33
34
|
*/
|
|
34
35
|
maxOverride?: number;
|
|
35
36
|
/**
|
|
@@ -96,7 +96,7 @@ export type { ToolboxLabels } from './toolbox/labels.js';
|
|
|
96
96
|
export type { EchartProps, EchartUIProps, EchartsEventHandler } from './echart/index.js';
|
|
97
97
|
export type { NoteProps, NoteMarkdownProps } from './note/widget-note.js';
|
|
98
98
|
export type { NoteLabels } from './note/labels.js';
|
|
99
|
-
export type { SearcherProps, SearcherToggleProps, StackToggleProps, ShowAllToggleProps, ZoomToggleProps, BrushToggleProps, RelativeDataProps, DownloadProps, DownloadItem, DownloadContext, FullScreenTriggerProps, FullScreenSlotProps, LockSelectionProps, LockSelectionKey, ChangeColumnProps, ChangeColumnItem, } from './actions/index.js';
|
|
99
|
+
export type { SearcherProps, SearcherToggleProps, StackToggleProps, ShowAllToggleProps, ZoomToggleProps, BrushToggleProps, RelativeDataProps, RelativeScale, DownloadProps, DownloadItem, DownloadContext, FullScreenTriggerProps, FullScreenSlotProps, LockSelectionProps, LockSelectionKey, ChangeColumnProps, ChangeColumnItem, } from './actions/index.js';
|
|
100
100
|
export type { FormulaUIProps, FormulaDataItem, FormulaWidgetData, } from './formula/index.js';
|
|
101
101
|
export type { MarkdownUIProps, MarkdownWidgetData } from './markdown/index.js';
|
|
102
102
|
export type { PieDatum, PieWidgetData } from './pie/index.js';
|
package/dist/widgets/actions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as e, E as t, S as n, T as r, _ as i, a, b as o, c as s, d as c, f as l, g as u, h as d, i as f, l as p, m, n as h, o as g, p as _, r as v, s as y, t as b, u as x, v as S, w as C, x as w, y as T } from "../actions-
|
|
1
|
+
import { C as e, E as t, S as n, T as r, _ as i, a, b as o, c as s, d as c, f as l, g as u, h as d, i as f, l as p, m, n as h, o as g, p as _, r as v, s as y, t as b, u as x, v as S, w as C, x as w, y as T } from "../actions-DyWNwvLu.js";
|
|
2
2
|
import { a as E, i as D, n as O, r as k, t as A } from "../zoom-toggle-MmLjMTEw.js";
|
|
3
3
|
import { a as j, c as M, d as N, f as P, i as F, l as I, n as L, o as R, p as z, r as B, s as V, t as H, u as U } from "../download-B4sykOFm.js";
|
|
4
4
|
export { l as BrushToggle, j as CSVIcon, b as ChangeColumn, _ as DEFAULT_BRUSH_TOGGLE_LABELS, h as DEFAULT_CHANGE_COLUMN_LABELS, I as DEFAULT_DOWNLOAD_LABELS, y as DEFAULT_FULLSCREEN_LABELS, f as DEFAULT_LOCK_SELECTION_LABELS, p as DEFAULT_RELATIVE_DATA_LABELS, r as DEFAULT_SEARCHER_LABELS, S as DEFAULT_SHOW_ALL_LABELS, o as DEFAULT_STACK_TOGGLE_LABELS, O as DEFAULT_ZOOM_TOGGLE_LABELS, F as DOWNLOAD_ITEM_IDS, M as Download, g as FullScreen, v as LockSelection, R as MarkdownIcon, V as PNGIcon, s as RelativeData, d as SHOW_ALL_ID, n as Searcher, e as SearcherToggle, u as ShowAllToggle, T as StackToggle, k as ZOOM_LAYOUT, A as ZoomToggle, m as addBrush, w as addStack, D as addZoom, L as buildCsvDownloadItem, B as buildPngDownloadItem, E as createAddZoom, x as createPercentFormatter, U as downloadDOMToPNG, N as downloadToCSV, a as filterByLockedItems, t as filterBySearchText, C as setSearcherText, i as setShowAll, P as toCsvString, c as toRelativeData, z as triggerLinkDownload, H as withPngItem };
|