import { useState } from 'react' import { Box, IconButton, InputAdornment, MenuItem, OutlinedInput, Slider, Typography, } from '@mui/material' import { Close as CloseIcon } from '@mui/icons-material' import { Tooltip } from '../../../components/tooltip/tooltip' import { useLegendId, useLegendShallow, useLegendStore } from '../../stores' import { useLegendConfig } from '../../provider' import { useLegendRow } from '../contexts' import { styles } from './styles' const clampPercent = (value: number): number => Math.min(100, Math.max(0, Math.round(value))) /** * Opacity menu item — toggles the row's inline bar; self-hides when hidden or no `opacityControl`. * * @experimental This API is new and may change in a future release. */ function OpacityItem() { const id = useLegendId() const row = useLegendRow() const layer = useLegendShallow(id, (s) => row ? s.layers[row.layerId] : undefined, ) const setCollapsed = useLegendShallow(id, (s) => s.setCollapsed) const { labels } = useLegendConfig() if (!row || !layer?.visible || !layer.opacityControl) return null const toggle = () => { const next = !row.opacityOpen row.setOpacityOpen(next) if (next && layer.collapsed) setCollapsed(row.layerId, false) } return ( {labels.opacity} ) } /** * Inline opacity bar (first body child of `Legend.Row`). Slider commits on release; input commits live. * * @experimental This API is new and may change in a future release. */ function OpacityInline() { const id = useLegendId() const row = useLegendRow() const layerId = row?.layerId const opacity = useLegendStore(id, (s) => layerId ? (s.layers[layerId]?.opacity ?? 1) : 1, ) const setOpacity = useLegendStore(id, (s) => s.setOpacity) const { labels } = useLegendConfig() const [dragPercent, setDragPercent] = useState(null) const [draft, setDraft] = useState(null) if (!row || !layerId || !row.opacityOpen) return null const percent = dragPercent ?? clampPercent(opacity * 100) const commitPercent = (value: number): number => { const clamped = clampPercent(value) setOpacity(layerId, clamped / 100) return clamped } const handleInputChange = (raw: string) => { setDraft(raw) if (raw.trim() === '') return const parsed = Number(raw) if (!Number.isFinite(parsed)) return const clamped = commitPercent(parsed) if (clamped !== parsed) setDraft(String(clamped)) } const handleClose = () => { row.setOpacityOpen(false) setDraft(null) setDragPercent(null) } return ( {labels.opacity} { setDragPercent(value as number) setDraft(null) }} onChangeCommitted={(_, value) => { commitPercent(value as number) setDragPercent(null) }} aria-label={labels.opacity} sx={styles.slider} /> handleInputChange(event.target.value)} onBlur={() => setDraft(null)} onKeyDown={(event) => { if (event.key === 'Enter') (event.target as HTMLElement).blur() }} endAdornment={%} inputProps={{ inputMode: 'numeric', 'aria-label': labels.opacity }} sx={styles.input} /> ) } /** * Compound opacity control: `Item` in the row menu, `Inline` in the body. * * @experimental This API is new and may change in a future release. */ export const LegendOpacity = { Item: OpacityItem, Inline: OpacityInline, }