@spear-ai/spectral 1.19.1 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"DirectionalColorWheel.js","names":[],"sources":["../src/components/DirectionalColorWheel/DirectionalColorWheel.tsx"],"sourcesContent":["import { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useId, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent, type PointerEvent as ReactPointerEvent, type Ref } from 'react'\nimport { annularSectorPath, normalizeBearing, polarToCartesian, resolveSectorColor, sectorBearingRange, sectorClipPath, sectorId, thresholdToSaturation, type DirectionalColorStop, type DirectionalSectorCount } from './directionalColorWheelMath'\n\nexport interface DirectionalColorWheelProps {\n /** Accessible name for the wheel group. */\n accessibleName?: string\n /**\n * Read-only bearing (degrees) the needle points at, using the compositor's `atan2(sine, cosine)`\n * convention (0° = N, clockwise). The consumer drives it (e.g. from the feed hover cursor) so the\n * needle follows the cursor; the wheel only displays it — there is no drag interaction.\n * `null`/`undefined` hides the needle — keep it nullable so Horizon can gate the feature flag with\n * a single `null` (PAT-028).\n */\n bearing?: number | null\n /** Layout-only class extension (margins/padding). */\n className?: string\n /**\n * \"Color Angle\": degrees the colours are rotated clockwise around the ring. Controlled+uncontrolled\n * (pair it with `defaultColorAngle` / `onColorAngleChange`). The colours can also be spun directly by\n * dragging the colour ring — that gesture reports through `onColorAngleChange` and is only active when\n * the value can take effect (uncontrolled, or controlled *with* an `onColorAngleChange` handler). Sector\n * geometry and the N/E/S/W bezel stay fixed to compass bearings; only the colours rotate.\n */\n colorAngle?: number\n /**\n * Advanced parity hook: resolver mapping a bearing in `[0, 360)` to a CSS colour. When\n * provided it wins over `colorStops`; pass the same resolver the feed colours by so the\n * legend samples the identical colours.\n */\n colorForBearing?: ((bearingDegrees: number) => string) | null\n /** Palette stops (position `0..1`). Defaults to the directional DIFAR palette. */\n colorStops?: DirectionalColorStop[] | null\n dataTestId?: string\n /** Uncontrolled initial Color Angle (degrees). */\n defaultColorAngle?: number\n /** Uncontrolled initial set of disabled (hidden) sector indices. */\n defaultDisabledSectors?: number[]\n /** Uncontrolled initial threshold. */\n defaultThreshold?: number\n disabled?: boolean\n /** Controlled set of disabled (hidden) sector indices, in `[0, sectorCount)`. */\n disabledSectors?: number[] | null\n /** Called with the next Color Angle (degrees, `[0, 360)`) as the colour ring is dragged. */\n onColorAngleChange?: (colorAngle: number) => void\n /** Called with the next set of disabled sector indices whenever a wedge is toggled. */\n onDisabledSectorsChange?: (disabledSectors: number[]) => void\n /** Called when a single wedge is toggled: its index and whether it is now disabled. */\n onSectorToggle?: (sectorIndex: number, nextDisabled: boolean) => void\n /** Called with the threshold (`0..1`) as the centre dial changes. */\n onThresholdChange?: (threshold: number) => void\n ref?: Ref<HTMLDivElement>\n /** Number of toggleable wedges. */\n sectorCount?: DirectionalSectorCount\n /** Diameter in pixels. */\n size?: number\n /**\n * Coherence threshold (`0..1`). Coherence is how reliable the bearing estimate is; this dial\n * maps it to saturation — higher threshold desaturates the legend. The component only reports\n * the value via `onThresholdChange`; the consumer decides what it drives.\n */\n threshold?: number\n /** Keyboard step for the threshold dial. */\n thresholdStep?: number\n}\n\n// viewBox is a 0..100 square; every radius is a fraction of the wheel. The ring sits well\n// inside the box so the N/E/S/W labels have clear breathing room outside it.\nconst CENTER = 50\nconst OUTER_RADIUS = 38\nconst INNER_RADIUS = 22\nconst LABEL_RADIUS = 47\n// The focus ring sits just inside the wedge with a minimal gap (~1-2px at typical sizes) so the\n// stroke never grazes the shared wedge edge or bleeds onto the neighbour (which left a focus remnant\n// when a wedge was re-enabled), while staying tight enough to read as that wedge's outline.\nconst FOCUS_RADIAL_INSET = 0.7\nconst FOCUS_ANGULAR_INSET = 1.1\n// The threshold dial's focus ring is an SVG circle just outside the hub, stroked with the same\n// width and viewBox units as the sector focus ring, so the two are identical at any `size`.\nconst THRESHOLD_FOCUS_RADIUS = (INNER_RADIUS * 1.7) / 2 + 0.8\nconst FOCUS_STROKE_WIDTH = 0.5\n// Read-only needle: a slim lance across the colour ring whose tip points at the bearing. It's\n// split down its centreline into a lighter and darker red facet (a compass-needle highlight) and\n// lifted off the ring with a soft drop-shadow rather than a hard outline.\nconst NEEDLE_TIP_RADIUS = 39\nconst NEEDLE_BASE_RADIUS = INNER_RADIUS\nconst NEEDLE_HALF_WIDTH_DEGREES = 3\n// Watch-style degree bezel just outside the colour ring: a tick every TICK_STEP_DEGREES, longer\n// at each 30° (which line up with the N/E/S/W labels). Labels sit further out (LABEL_RADIUS) so\n// the ticks never crowd them.\nconst TICK_OUTER_RADIUS = 43\nconst TICK_INNER_MAJOR = 39\nconst TICK_INNER_MINOR = 41\nconst TICK_STEP_DEGREES = 10\n// The threshold is shown as a concentric ring inside the hub: radius 0 (a point at the centre)\n// at threshold 0, growing to this fraction of the hub radius at threshold 1.\nconst THRESHOLD_TRACK_FRACTION = 0.82\n// 0° = North (top), increasing clockwise — the compass convention the colours also use.\nconst CARDINAL_LABELS: Record<number, string> = { 0: 'N', 90: 'E', 180: 'S', 270: 'W' }\n// Precomputed once (geometry only): the degree-bezel tick lines, longer at each 30°.\nconst DEGREE_TICKS = Array.from({ length: 360 / TICK_STEP_DEGREES }, (_, index) => {\n const bearing = index * TICK_STEP_DEGREES\n const isMajor = bearing % 30 === 0\n return {\n bearing,\n inner: polarToCartesian(CENTER, CENTER, isMajor ? TICK_INNER_MAJOR : TICK_INNER_MINOR, bearing),\n isMajor,\n outer: polarToCartesian(CENTER, CENTER, TICK_OUTER_RADIUS, bearing),\n }\n})\n// One label per 30° major tick: the cardinal letter (N/E/S/W) where there is one, otherwise the\n// bearing in degrees. Sits at LABEL_RADIUS, outside the tick bezel.\nconst BEZEL_LABELS = Array.from({ length: 12 }, (_, index) => {\n const bearing = index * 30\n const cardinal = CARDINAL_LABELS[bearing]\n return {\n at: polarToCartesian(CENTER, CENTER, LABEL_RADIUS, bearing),\n bearing,\n isCardinal: cardinal !== undefined,\n text: cardinal ?? String(bearing),\n }\n})\n\nconst clampUnit = (value: number): number => Math.max(0, Math.min(1, value))\n\n// Threshold from how far the pointer sits *above* the hub centre, as a fraction of the ring's\n// max radius: 0 at/below the centre, 1 at the top of the track. Signed-and-clamped (not radial\n// distance) so the ring shrinks to a point and stops dead at the centre instead of bouncing\n// back out the other side. `setThresholdClamped` floors the negative (below-centre) values at 0.\nconst pointerThreshold = (clientY: number, rect: DOMRect): number => {\n const deltaY = clientY - (rect.top + rect.height / 2)\n const maxRadiusPx = ((INNER_RADIUS * 1.7) / 100 / 2) * rect.width * THRESHOLD_TRACK_FRACTION\n return -deltaY / maxRadiusPx\n}\n\n// Drag distance (px) below which a press is a sector tap, not a ring rotation.\nconst ROTATE_SLOP_PX = 4\n\n// Pointer's compass bearing about the wheel centre, matching `polarToCartesian` (atan2(dx, −dy)).\nconst pointerBearing = (clientX: number, clientY: number, rect: DOMRect): number => {\n const deltaX = clientX - (rect.left + rect.width / 2)\n const deltaY = clientY - (rect.top + rect.height / 2)\n return normalizeBearing((Math.atan2(deltaX, -deltaY) * 180) / Math.PI)\n}\n\nexport const DirectionalColorWheel = ({\n accessibleName = 'Directional bearing color wheel',\n bearing,\n className,\n colorAngle,\n colorForBearing,\n colorStops,\n dataTestId = 'spectral-directional-color-wheel',\n defaultColorAngle,\n defaultDisabledSectors,\n defaultThreshold = 0,\n disabled = false,\n disabledSectors,\n onColorAngleChange,\n onDisabledSectorsChange,\n onSectorToggle,\n onThresholdChange,\n ref,\n sectorCount = 12,\n size = 240,\n threshold,\n thresholdStep = 0.05,\n}: DirectionalColorWheelProps) => {\n const rootRef = useRef<HTMLDivElement>(null)\n const wedgeRefs = useRef<(HTMLButtonElement | null)[]>([])\n const thresholdInputRef = useRef<HTMLInputElement>(null)\n const dialRectRef = useRef<DOMRect | null>(null)\n const needleShadowId = `directional-needle-shadow-${useId().replaceAll(':', '')}`\n\n const [thresholdValue, setThresholdValue] = useUncontrolledState<number>({ defaultValue: clampUnit(defaultThreshold), onChange: onThresholdChange, value: threshold })\n const [disabledValue, setDisabledValue] = useUncontrolledState<number[]>({ defaultValue: defaultDisabledSectors ?? [], onChange: onDisabledSectorsChange, value: disabledSectors ?? undefined })\n const [colorAngleValue, setColorAngleValue] = useUncontrolledState<number>({ defaultValue: defaultColorAngle ?? 0, onChange: onColorAngleChange, value: colorAngle })\n\n const [activeSectorIndex, setActiveSectorIndex] = useState(0)\n const [focusedSectorIndex, setFocusedSectorIndex] = useState<number | null>(null)\n const [hoveredSectorIndex, setHoveredSectorIndex] = useState<number | null>(null)\n const [draggingDial, setDraggingDial] = useState(false)\n const [dialFocused, setDialFocused] = useState(false)\n const [rotatingRing, setRotatingRing] = useState(false)\n\n // Live rotation drag (a ref so rapid moves don't hinge on re-render timing).\n const rotationRef = useRef<{ angle: number; lastBearing: number; moved: boolean; pointerId: number; rect: DOMRect; startX: number; startY: number; wedge: HTMLButtonElement } | null>(null)\n const suppressClickRef = useRef(false)\n\n // Dragging is enabled only when the value can take effect (uncontrolled, or controlled with a handler).\n const canRotate = !disabled && (colorAngle === undefined || onColorAngleChange !== undefined)\n\n const disabledSet = useMemo(() => new Set(disabledValue.filter((index) => index >= 0 && index < sectorCount)), [disabledValue, sectorCount])\n\n // Flat GRAMS-style wedges, palette sampled at each wedge centre; no divider stroke → seamless ring.\n // Geometry-only, so memoised (not rebuilt on every threshold/hover/focus render).\n const sectors = useMemo(\n () =>\n Array.from({ length: sectorCount }, (_, index) => {\n const range = sectorBearingRange(index, sectorCount)\n return {\n ...range,\n clipPath: sectorClipPath(INNER_RADIUS, OUTER_RADIUS, range.startBearing, range.endBearing),\n color: resolveSectorColor({ colorForBearing, colorStops }, (range.startBearing + range.endBearing) / 2, colorAngleValue),\n id: sectorId(index, sectorCount),\n index,\n }\n }),\n [colorAngleValue, colorForBearing, colorStops, sectorCount],\n )\n\n const commitDisabled = useCallback(\n (index: number) => {\n if (disabled) return\n const next = disabledSet.has(index) ? disabledValue.filter((value) => value !== index) : [...disabledValue.filter((value) => value >= 0 && value < sectorCount), index].sort((first, second) => first - second)\n setDisabledValue(next)\n onSectorToggle?.(index, !disabledSet.has(index))\n },\n [disabled, disabledSet, disabledValue, onSectorToggle, sectorCount, setDisabledValue],\n )\n\n const handleRingPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>) => {\n // Reset so a prior drag never swallows this interaction's click.\n suppressClickRef.current = false\n if (!canRotate) return\n const rect = rootRef.current?.getBoundingClientRect()\n if (!rect) return\n try {\n event.currentTarget.setPointerCapture(event.pointerId)\n } catch {\n // setPointerCapture can throw without an active pointer (e.g. synthetic test events).\n }\n rotationRef.current = { angle: colorAngleValue, lastBearing: pointerBearing(event.clientX, event.clientY, rect), moved: false, pointerId: event.pointerId, rect, startX: event.clientX, startY: event.clientY, wedge: event.currentTarget }\n },\n [canRotate, colorAngleValue],\n )\n\n const handleRingPointerMove = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>) => {\n const drag = rotationRef.current\n if (drag === null || drag.pointerId !== event.pointerId) return\n // Cached rect (the wheel doesn't move mid-drag) avoids a layout read per move.\n const bearingNow = pointerBearing(event.clientX, event.clientY, drag.rect)\n // Signed, wrap-safe step from the last sample (supports multi-turn spins).\n let step = bearingNow - drag.lastBearing\n if (step > 180) step -= 360\n if (step < -180) step += 360\n drag.lastBearing = bearingNow\n drag.angle = normalizeBearing(drag.angle + step)\n if (!drag.moved) {\n if (Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) <= ROTATE_SLOP_PX) return\n drag.moved = true\n suppressClickRef.current = true\n setRotatingRing(true)\n // Drop the wedge focus so no accent ring lingers on a sector while the ring spins.\n drag.wedge.blur()\n }\n setColorAngleValue(drag.angle)\n },\n [setColorAngleValue],\n )\n\n const endRingRotation = useCallback(() => {\n rotationRef.current = null\n setRotatingRing(false)\n }, [])\n\n const focusSector = useCallback((index: number) => {\n setActiveSectorIndex(index)\n wedgeRefs.current[index]?.focus()\n }, [])\n\n const handleWedgeKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLButtonElement>, index: number) => {\n if (disabled) return\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown': {\n event.preventDefault()\n focusSector((index + 1) % sectorCount)\n break\n }\n case 'ArrowLeft':\n case 'ArrowUp': {\n event.preventDefault()\n focusSector((index - 1 + sectorCount) % sectorCount)\n break\n }\n case 'Home': {\n event.preventDefault()\n focusSector(0)\n break\n }\n case 'End': {\n event.preventDefault()\n focusSector(sectorCount - 1)\n break\n }\n default: {\n break\n }\n }\n },\n [disabled, focusSector, sectorCount],\n )\n\n const setThresholdClamped = useCallback(\n (next: number) => {\n setThresholdValue(clampUnit(next))\n },\n [setThresholdValue],\n )\n\n const handleThresholdKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowUp': {\n event.preventDefault()\n setThresholdClamped(thresholdValue + thresholdStep)\n break\n }\n case 'ArrowLeft':\n case 'ArrowDown': {\n event.preventDefault()\n setThresholdClamped(thresholdValue - thresholdStep)\n break\n }\n case 'PageUp': {\n event.preventDefault()\n setThresholdClamped(thresholdValue + thresholdStep * 10)\n break\n }\n case 'PageDown': {\n event.preventDefault()\n setThresholdClamped(thresholdValue - thresholdStep * 10)\n break\n }\n case 'Home': {\n event.preventDefault()\n setThresholdClamped(0)\n break\n }\n case 'End': {\n event.preventDefault()\n setThresholdClamped(1)\n break\n }\n default: {\n break\n }\n }\n },\n [setThresholdClamped, thresholdStep, thresholdValue],\n )\n\n const handleDialPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (disabled) return\n // Suppress the default focus shift (a mousedown on this non-focusable div would otherwise\n // pull focus to the body) so the slider focus below sticks.\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n setDraggingDial(true)\n // Focus the (hidden) slider so the value is immediately arrow-key adjustable after a click.\n thresholdInputRef.current?.focus()\n // Cache the rect for the drag (the wheel doesn't move mid-drag) to avoid a layout read per move.\n const rect = rootRef.current?.getBoundingClientRect() ?? null\n dialRectRef.current = rect\n if (rect) setThresholdClamped(pointerThreshold(event.clientY, rect))\n },\n [disabled, setThresholdClamped],\n )\n\n const handleDialPointerMove = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n const rect = dialRectRef.current\n if (!draggingDial || !rect) return\n setThresholdClamped(pointerThreshold(event.clientY, rect))\n },\n [draggingDial, setThresholdClamped],\n )\n\n const handleRef = useCallback(\n (node: HTMLDivElement | null) => {\n rootRef.current = node\n if (typeof ref === 'function') {\n ref(node)\n } else if (ref) {\n ref.current = node\n }\n },\n [ref],\n )\n\n // Ring + handle in hub-local % (centre 50,50): the ring's diameter grows with the value; the\n // dot sits at its top. Threshold 0 → diameter 0 (dot at the centre); 1 → the track edge.\n // Clamp for rendering: a controlled `threshold` outside [0,1] must not overflow the ring/dot\n // or desync the native input (which clamps) from the announced aria-valuetext.\n const clampedThreshold = clampUnit(thresholdValue)\n const ringDiameterPercent = clampedThreshold * 100 * THRESHOLD_TRACK_FRACTION\n const dotTopPercent = 50 - clampedThreshold * 50 * THRESHOLD_TRACK_FRACTION\n // Hover fill is for the hovered wedge only — never the focused one, which already shows the\n // accent focus ring. Otherwise a just-toggled wedge (focused + hovered) gets fill + ring, which\n // reads as a doubled outline.\n const hoverHighlightIndex = !rotatingRing && hoveredSectorIndex !== null && hoveredSectorIndex !== focusedSectorIndex ? hoveredSectorIndex : null\n // The roving-tabindex anchor must stay valid if sectorCount shrinks below it, or the whole\n // wedge group would drop out of the tab order (no button matching a stale index).\n const safeActiveIndex = Math.min(activeSectorIndex, sectorCount - 1)\n const saturation = thresholdToSaturation(clampedThreshold)\n\n // Read-only needle: shown only when a finite bearing is supplied (consumer-driven, e.g. the feed\n // hover cursor). A slim triangle pointing out to the rim at that compass bearing.\n const hasNeedle = typeof bearing === 'number' && Number.isFinite(bearing)\n const needleBearing = hasNeedle ? normalizeBearing(bearing as number) : 0\n const needleTip = polarToCartesian(CENTER, CENTER, NEEDLE_TIP_RADIUS, needleBearing)\n const needleBaseMid = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, needleBearing)\n const needleBaseLeft = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, needleBearing - NEEDLE_HALF_WIDTH_DEGREES)\n const needleBaseRight = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, needleBearing + NEEDLE_HALF_WIDTH_DEGREES)\n\n return (\n <div\n aria-disabled={disabled || undefined}\n aria-label={accessibleName}\n className={cn('relative inline-grid select-none', disabled && 'pointer-events-none opacity-50', className)}\n data-disabled={disabled || undefined}\n data-slot='directional-color-wheel'\n data-testid={dataTestId}\n ref={handleRef}\n role='group'\n style={{ height: size, width: size }}\n >\n {/* Flat coloured sector wedges (the GRAMS look): clipped, toggleable buttons. The\n coherence→saturation preview is one `saturate()` on the whole group (not per wedge), so the\n ring is a single stacking context the overlay can paint over — disabled wedges are grey, so\n the filter is a no-op on them. */}\n <div\n className='col-start-1 row-start-1 grid size-full'\n data-rotatable={canRotate || undefined}\n data-rotating={rotatingRing || undefined}\n data-slot='directional-color-wheel-sectors'\n data-testid={`${dataTestId}-sectors`}\n style={{ filter: `saturate(${saturation})` }}\n >\n {sectors.map((sector) => {\n const isEnabled = !disabledSet.has(sector.index)\n return (\n <button\n aria-label={`Bearing sector ${Math.round(sector.startBearing)}° to ${Math.round(sector.endBearing)}°`}\n aria-pressed={isEnabled}\n className={cn('col-start-1 row-start-1 size-full cursor-pointer outline-none motion-safe:transition-opacity motion-safe:duration-150', !isEnabled && 'opacity-25', canRotate && 'touch-none', canRotate && (rotatingRing ? 'cursor-grabbing' : 'cursor-grab'))}\n data-sector-id={sector.id}\n data-slot='directional-color-wheel-sector'\n data-testid={`${dataTestId}-sector-${sector.index}`}\n disabled={disabled}\n key={sector.id}\n onBlur={() => {\n setFocusedSectorIndex((current) => (current === sector.index ? null : current))\n }}\n onClick={() => {\n // Swallow the click after a rotation drag so it doesn't also toggle.\n if (suppressClickRef.current) {\n suppressClickRef.current = false\n return\n }\n commitDisabled(sector.index)\n }}\n onFocus={() => {\n setActiveSectorIndex(sector.index)\n setFocusedSectorIndex(sector.index)\n }}\n onKeyDown={(event) => {\n handleWedgeKeyDown(event, sector.index)\n }}\n onLostPointerCapture={endRingRotation}\n onPointerDown={handleRingPointerDown}\n onPointerEnter={() => {\n setHoveredSectorIndex(sector.index)\n }}\n onPointerLeave={() => {\n setHoveredSectorIndex((current) => (current === sector.index ? null : current))\n }}\n onPointerMove={handleRingPointerMove}\n onPointerUp={endRingRotation}\n ref={(node) => {\n wedgeRefs.current[sector.index] = node\n }}\n style={{ background: isEnabled ? sector.color : 'var(--color-level-one)', clipPath: sector.clipPath }}\n tabIndex={disabled || sector.index !== safeActiveIndex ? -1 : 0}\n type='button'\n />\n )\n })}\n </div>\n\n {/* Decorative + interactive overlay, painted above the wedge group (z-10): ring outlines,\n degree bezel, hover/focus highlight, and the compass/degree labels. */}\n <svg\n aria-hidden\n className='pointer-events-none z-10 col-start-1 row-start-1 size-full overflow-visible'\n data-slot='directional-color-wheel-overlay'\n data-testid={`${dataTestId}-overlay`}\n viewBox='0 0 100 100'\n >\n <circle\n className='fill-none stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n r={OUTER_RADIUS}\n strokeWidth={0.6}\n />\n <circle\n className='fill-none stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n r={INNER_RADIUS}\n strokeWidth={0.6}\n />\n <circle\n className='fill-none stroke-border-primary/50'\n cx={CENTER}\n cy={CENTER}\n r={TICK_OUTER_RADIUS}\n strokeWidth={0.4}\n />\n <g data-testid={`${dataTestId}-degree-ticks`}>\n {DEGREE_TICKS.map((tick) => (\n <line\n className={tick.isMajor ? 'stroke-text-secondary' : 'stroke-border-primary/80'}\n key={tick.bearing}\n strokeLinecap='round'\n strokeWidth={tick.isMajor ? 0.8 : 0.4}\n x1={tick.inner.x}\n x2={tick.outer.x}\n y1={tick.inner.y}\n y2={tick.outer.y}\n />\n ))}\n </g>\n {hoverHighlightIndex !== null && sectors[hoverHighlightIndex] ? (\n <path\n className='fill-text-primary/15'\n d={annularSectorPath(CENTER, CENTER, INNER_RADIUS, OUTER_RADIUS, sectors[hoverHighlightIndex].startBearing, sectors[hoverHighlightIndex].endBearing)}\n />\n ) : null}\n {focusedSectorIndex !== null && !rotatingRing && sectors[focusedSectorIndex] ? (\n <path\n className='fill-none stroke-accent'\n d={annularSectorPath(CENTER, CENTER, INNER_RADIUS + FOCUS_RADIAL_INSET, OUTER_RADIUS - FOCUS_RADIAL_INSET, sectors[focusedSectorIndex].startBearing + FOCUS_ANGULAR_INSET, sectors[focusedSectorIndex].endBearing - FOCUS_ANGULAR_INSET)}\n strokeLinejoin='round'\n strokeWidth={FOCUS_STROKE_WIDTH}\n />\n ) : null}\n {BEZEL_LABELS.map((label) => (\n <text\n className={label.isCardinal ? 'font-medium fill-text-secondary text-[6px]' : 'fill-text-secondary text-[4px] tabular-nums'}\n dominantBaseline='central'\n key={label.bearing}\n textAnchor='middle'\n x={label.at.x}\n y={label.at.y}\n >\n {label.text}\n </text>\n ))}\n </svg>\n\n {/* Threshold: a gray hub holding a concentric ring whose radius is the value. Drag the red\n handle dot out (or arrow-key the hidden slider) to grow the ring; it shrinks to a point\n and stops at the centre (0). The track edge is 1. */}\n <div\n className={cn('relative z-20 col-start-1 row-start-1 m-auto flex touch-none items-center justify-center rounded-full border border-border-primary shadow-elevation-2', draggingDial ? 'cursor-grabbing' : 'cursor-grab')}\n data-slot='directional-color-wheel-threshold-dial'\n data-testid={`${dataTestId}-threshold-dial`}\n onLostPointerCapture={() => {\n setDraggingDial(false)\n }}\n onPointerDown={handleDialPointerDown}\n onPointerMove={handleDialPointerMove}\n onPointerUp={() => {\n setDraggingDial(false)\n }}\n style={{ background: 'radial-gradient(circle at 38% 30%, var(--color-level-four), var(--color-level-one) 82%)', height: `${INNER_RADIUS * 1.7}%`, width: `${INNER_RADIUS * 1.7}%` }}\n >\n <span\n aria-hidden\n className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-text-secondary/40'\n data-slot='directional-color-wheel-threshold-ring'\n style={{ height: `${ringDiameterPercent}%`, width: `${ringDiameterPercent}%` }}\n />\n <span\n aria-hidden\n className='absolute left-1/2 size-[15%] -translate-x-1/2 -translate-y-1/2 rounded-full border border-border-primary bg-danger-400 shadow-elevation-1'\n data-slot='directional-color-wheel-threshold-indicator'\n data-testid={`${dataTestId}-threshold-indicator`}\n style={{ top: `${dotTopPercent}%` }}\n />\n <input\n aria-label='Coherence threshold'\n aria-valuetext={`${Math.round(clampedThreshold * 100)} percent`}\n className='sr-only'\n data-slot='directional-color-wheel-threshold-input'\n data-testid={`${dataTestId}-threshold`}\n disabled={disabled}\n max={1}\n min={0}\n onBlur={() => {\n setDialFocused(false)\n }}\n onChange={(event) => {\n setThresholdClamped(Number(event.target.value))\n }}\n onFocus={() => {\n setDialFocused(true)\n }}\n onKeyDown={handleThresholdKeyDown}\n ref={thresholdInputRef}\n step={thresholdStep}\n tabIndex={disabled ? -1 : 0}\n type='range'\n value={clampedThreshold}\n />\n </div>\n\n {/* Top overlay above the hub (z-30): the threshold focus ring (an SVG circle matching the\n sector focus ring's stroke width/units exactly) and the read-only bearing needle. */}\n {dialFocused || hasNeedle ? (\n <svg\n aria-hidden\n className='pointer-events-none z-30 col-start-1 row-start-1 size-full overflow-visible'\n viewBox='0 0 100 100'\n >\n {hasNeedle ? (\n <defs>\n <filter\n filterUnits='userSpaceOnUse'\n height='100'\n id={needleShadowId}\n width='100'\n x='0'\n y='0'\n >\n <feDropShadow\n dx='0'\n dy='0.3'\n floodColor='var(--color-level-one)'\n floodOpacity='0.55'\n stdDeviation='0.5'\n />\n </filter>\n </defs>\n ) : null}\n {dialFocused ? (\n <circle\n className='fill-none stroke-accent'\n cx={CENTER}\n cy={CENTER}\n r={THRESHOLD_FOCUS_RADIUS}\n strokeWidth={FOCUS_STROKE_WIDTH}\n />\n ) : null}\n {hasNeedle ? (\n <g\n data-testid={`${dataTestId}-needle`}\n filter={`url(#${needleShadowId})`}\n >\n {/* Read-only bearing needle (consumer-driven; HZN-2658 follows the cursor). Two facets\n split along the centreline give it a lit/shadowed compass look; the drop-shadow\n lifts it off any wedge colour. Decorative — the consumer surfaces the numeric\n bearing in the hover label. */}\n <polygon\n className='fill-danger-500'\n points={`${needleTip.x},${needleTip.y} ${needleBaseLeft.x},${needleBaseLeft.y} ${needleBaseMid.x},${needleBaseMid.y}`}\n />\n <polygon\n className='fill-danger-400'\n points={`${needleTip.x},${needleTip.y} ${needleBaseMid.x},${needleBaseMid.y} ${needleBaseRight.x},${needleBaseRight.y}`}\n />\n </g>\n ) : null}\n </svg>\n ) : null}\n\n {/* The needle SVG is decorative (aria-hidden), so expose the bearing as text for assistive tech. */}\n {hasNeedle ? (\n <span\n className='sr-only'\n data-testid={`${dataTestId}-bearing-value`}\n >\n Bearing {Math.round(needleBearing)} degrees\n </span>\n ) : null}\n </div>\n )\n}\n\nDirectionalColorWheel.displayName = 'DirectionalColorWheel'\n"],"mappings":"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"}
1
+ {"version":3,"file":"DirectionalColorWheel.js","names":[],"sources":["../src/components/DirectionalColorWheel/DirectionalColorWheel.tsx"],"sourcesContent":["import { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useId, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent, type PointerEvent as ReactPointerEvent, type Ref } from 'react'\nimport { annularSectorPath, normalizeBearing, polarToCartesian, resolveSectorColor, sectorBearingRange, sectorClipPath, sectorId, thresholdToSaturation, type DirectionalColorStop, type DirectionalSectorCount } from './directionalColorWheelMath'\n\nexport interface DirectionalColorWheelProps {\n /** Accessible name for the wheel group. */\n accessibleName?: string\n /**\n * Read-only bearing (degrees) the needle points at, using the compositor's `atan2(sine, cosine)`\n * convention (0° = N, clockwise). The consumer drives it (e.g. from the feed hover cursor) so the\n * needle follows the cursor; the wheel only displays it — there is no drag interaction.\n * `null`/`undefined` hides the needle — keep it nullable so Horizon can gate the feature flag with\n * a single `null` (PAT-028).\n */\n bearing?: number | null\n /** Layout-only class extension (margins/padding). */\n className?: string\n /**\n * \"Color Angle\": degrees the colours are rotated clockwise around the ring. Controlled+uncontrolled\n * (pair it with `defaultColorAngle` / `onColorAngleChange`). The colours can also be spun directly by\n * dragging the colour ring — that gesture reports through `onColorAngleChange` and is only active when\n * the value can take effect (uncontrolled, or controlled *with* an `onColorAngleChange` handler). Sector\n * geometry and the N/E/S/W bezel stay fixed to compass bearings; only the colours rotate.\n */\n colorAngle?: number\n /**\n * Advanced parity hook: resolver mapping a bearing in `[0, 360)` to a CSS colour. When\n * provided it wins over `colorStops`; pass the same resolver the feed colours by so the\n * legend samples the identical colours.\n */\n colorForBearing?: ((bearingDegrees: number) => string) | null\n /** Palette stops (position `0..1`). Defaults to the directional DIFAR palette. */\n colorStops?: DirectionalColorStop[] | null\n dataTestId?: string\n /** Uncontrolled initial Color Angle (degrees). */\n defaultColorAngle?: number\n /** Uncontrolled initial set of disabled (hidden) sector indices. */\n defaultDisabledSectors?: number[]\n /** Uncontrolled initial threshold. */\n defaultThreshold?: number\n disabled?: boolean\n /** Controlled set of disabled (hidden) sector indices, in `[0, sectorCount)`. */\n disabledSectors?: number[] | null\n /** Called with the next Color Angle (degrees, `[0, 360)`) as the colour ring is dragged. */\n onColorAngleChange?: (colorAngle: number) => void\n /** Called with the next set of disabled sector indices whenever a wedge is toggled. */\n onDisabledSectorsChange?: (disabledSectors: number[]) => void\n /** Called when a single wedge is toggled: its index and whether it is now disabled. */\n onSectorToggle?: (sectorIndex: number, nextDisabled: boolean) => void\n /** Called with the threshold (`0..1`) as the centre dial changes. */\n onThresholdChange?: (threshold: number) => void\n ref?: Ref<HTMLDivElement>\n /** Number of toggleable wedges. */\n sectorCount?: DirectionalSectorCount\n /** Diameter in pixels. */\n size?: number\n /**\n * Coherence threshold (`0..1`). Coherence is how reliable the bearing estimate is; this dial\n * maps it to saturation — higher threshold desaturates the legend. The component only reports\n * the value via `onThresholdChange`; the consumer decides what it drives.\n */\n threshold?: number\n /** Keyboard step for the threshold dial. */\n thresholdStep?: number\n}\n\n// viewBox is a 0..100 square; every radius is a fraction of the wheel. The ring sits well\n// inside the box so the N/E/S/W labels have clear breathing room outside it.\nconst CENTER = 50\nconst OUTER_RADIUS = 38\nconst INNER_RADIUS = 22\nconst LABEL_RADIUS = 47\n// The focus ring sits just inside the wedge with a minimal gap (~1-2px at typical sizes) so the\n// stroke never grazes the shared wedge edge or bleeds onto the neighbour (which left a focus remnant\n// when a wedge was re-enabled), while staying tight enough to read as that wedge's outline.\nconst FOCUS_RADIAL_INSET = 0.7\nconst FOCUS_ANGULAR_INSET = 1.1\n// The threshold dial's focus ring is an SVG circle just outside the hub, stroked with the same\n// width and viewBox units as the sector focus ring, so the two are identical at any `size`.\nconst THRESHOLD_FOCUS_RADIUS = (INNER_RADIUS * 1.7) / 2 + 0.8\nconst FOCUS_STROKE_WIDTH = 0.5\n// Read-only needle: a slim lance across the colour ring whose tip points at the bearing. It's\n// split down its centreline into a lighter and darker red facet (a compass-needle highlight) and\n// lifted off the ring with a soft drop-shadow rather than a hard outline.\nconst NEEDLE_TIP_RADIUS = 39\nconst NEEDLE_BASE_RADIUS = INNER_RADIUS\nconst NEEDLE_HALF_WIDTH_DEGREES = 3\n// Watch-style degree bezel just outside the colour ring: a tick every TICK_STEP_DEGREES, longer\n// at each 30° (which line up with the N/E/S/W labels). Labels sit further out (LABEL_RADIUS) so\n// the ticks never crowd them.\nconst TICK_OUTER_RADIUS = 43\nconst TICK_INNER_MAJOR = 39\nconst TICK_INNER_MINOR = 41\nconst TICK_STEP_DEGREES = 10\n// The threshold is shown as a concentric ring inside the hub: radius 0 (a point at the centre)\n// at threshold 0, growing to this fraction of the hub radius at threshold 1.\nconst THRESHOLD_TRACK_FRACTION = 0.82\n// 0° = North (top), increasing clockwise — the compass convention the colours also use.\nconst CARDINAL_LABELS: Record<number, string> = { 0: 'N', 90: 'E', 180: 'S', 270: 'W' }\n// Precomputed once (geometry only): the degree-bezel tick lines, longer at each 30°.\nconst DEGREE_TICKS = Array.from({ length: 360 / TICK_STEP_DEGREES }, (_, index) => {\n const bearing = index * TICK_STEP_DEGREES\n const isMajor = bearing % 30 === 0\n return {\n bearing,\n inner: polarToCartesian(CENTER, CENTER, isMajor ? TICK_INNER_MAJOR : TICK_INNER_MINOR, bearing),\n isMajor,\n outer: polarToCartesian(CENTER, CENTER, TICK_OUTER_RADIUS, bearing),\n }\n})\n// One label per 30° major tick: the cardinal letter (N/E/S/W) where there is one, otherwise the\n// bearing in degrees. Sits at LABEL_RADIUS, outside the tick bezel.\nconst BEZEL_LABELS = Array.from({ length: 12 }, (_, index) => {\n const bearing = index * 30\n const cardinal = CARDINAL_LABELS[bearing]\n return {\n at: polarToCartesian(CENTER, CENTER, LABEL_RADIUS, bearing),\n bearing,\n isCardinal: cardinal !== undefined,\n text: cardinal ?? String(bearing),\n }\n})\n\nconst clampUnit = (value: number): number => Math.max(0, Math.min(1, value))\n\n// Threshold from how far the pointer sits *above* the hub centre, as a fraction of the ring's\n// max radius: 0 at/below the centre, 1 at the top of the track. Signed-and-clamped (not radial\n// distance) so the ring shrinks to a point and stops dead at the centre instead of bouncing\n// back out the other side. `setThresholdClamped` floors the negative (below-centre) values at 0.\nconst pointerThreshold = (clientY: number, rect: DOMRect): number => {\n const deltaY = clientY - (rect.top + rect.height / 2)\n const maxRadiusPx = ((INNER_RADIUS * 1.7) / 100 / 2) * rect.width * THRESHOLD_TRACK_FRACTION\n return -deltaY / maxRadiusPx\n}\n\n// Drag distance (px) below which a press is a sector tap, not a ring rotation.\nconst ROTATE_SLOP_PX = 4\n\n// Pointer's compass bearing about the wheel centre, matching `polarToCartesian` (atan2(dx, −dy)).\nconst pointerBearing = (clientX: number, clientY: number, rect: DOMRect): number => {\n const deltaX = clientX - (rect.left + rect.width / 2)\n const deltaY = clientY - (rect.top + rect.height / 2)\n return normalizeBearing((Math.atan2(deltaX, -deltaY) * 180) / Math.PI)\n}\n\n// Which sector a compass bearing falls in, for `count` equal sectors starting at 0° (matches `sectorBearingRange`).\nconst sectorIndexForBearing = (bearing: number, count: number): number => Math.floor(normalizeBearing(bearing) / (360 / count)) % count\n\nexport const DirectionalColorWheel = ({\n accessibleName = 'Directional bearing color wheel',\n bearing,\n className,\n colorAngle,\n colorForBearing,\n colorStops,\n dataTestId = 'spectral-directional-color-wheel',\n defaultColorAngle,\n defaultDisabledSectors,\n defaultThreshold = 0,\n disabled = false,\n disabledSectors,\n onColorAngleChange,\n onDisabledSectorsChange,\n onSectorToggle,\n onThresholdChange,\n ref,\n sectorCount = 12,\n size = 240,\n threshold,\n thresholdStep = 0.05,\n}: DirectionalColorWheelProps) => {\n const rootRef = useRef<HTMLDivElement>(null)\n const wedgeRefs = useRef<(HTMLButtonElement | null)[]>([])\n const thresholdInputRef = useRef<HTMLInputElement>(null)\n const dialRectRef = useRef<DOMRect | null>(null)\n const needleShadowId = `directional-needle-shadow-${useId().replaceAll(':', '')}`\n\n const [thresholdValue, setThresholdValue] = useUncontrolledState<number>({ defaultValue: clampUnit(defaultThreshold), onChange: onThresholdChange, value: threshold })\n const [disabledValue, setDisabledValue] = useUncontrolledState<number[]>({ defaultValue: defaultDisabledSectors ?? [], onChange: onDisabledSectorsChange, value: disabledSectors ?? undefined })\n const [colorAngleValue, setColorAngleValue] = useUncontrolledState<number>({ defaultValue: defaultColorAngle ?? 0, onChange: onColorAngleChange, value: colorAngle })\n\n const [activeSectorIndex, setActiveSectorIndex] = useState(0)\n const [focusedSectorIndex, setFocusedSectorIndex] = useState<number | null>(null)\n const [hoveredSectorIndex, setHoveredSectorIndex] = useState<number | null>(null)\n const [draggingDial, setDraggingDial] = useState(false)\n const [dialFocused, setDialFocused] = useState(false)\n const [rotatingRing, setRotatingRing] = useState(false)\n const [brushing, setBrushing] = useState(false)\n // The sectors swept by the in-progress brush stroke — accent-ringed as one active selection.\n const [brushedIndices, setBrushedIndices] = useState<number[]>([])\n\n // Live rotation drag (a ref so rapid moves don't hinge on re-render timing).\n const rotationRef = useRef<{ angle: number; lastBearing: number; moved: boolean; pointerId: number; rect: DOMRect; startX: number; startY: number; wedge: HTMLButtonElement } | null>(null)\n // Live shift+drag brush: paint every wedge the pointer crosses to one target state. The working\n // disabled set + the set swept so far live in a ref so the stroke stays self-consistent even before\n // state echoes back; the cached rect + last bearing let each move fill in every sector swept since\n // the previous sample (so a fast drag never skips a wedge — the gesture is geometric, not per-wedge\n // `pointerenter`).\n const brushRef = useRef<{ brushed: Set<number>; disabled: Set<number>; lastBearing: number; pointerId: number; rect: DOMRect; targetDisabled: boolean } | null>(null)\n const suppressClickRef = useRef(false)\n\n // Dragging is enabled only when the value can take effect (uncontrolled, or controlled with a handler).\n const canRotate = !disabled && (colorAngle === undefined || onColorAngleChange !== undefined)\n\n const disabledSet = useMemo(() => new Set(disabledValue.filter((index) => index >= 0 && index < sectorCount)), [disabledValue, sectorCount])\n\n // Flat GRAMS-style wedges, palette sampled at each wedge centre; no divider stroke → seamless ring.\n // Geometry-only, so memoised (not rebuilt on every threshold/hover/focus render).\n const sectors = useMemo(\n () =>\n Array.from({ length: sectorCount }, (_, index) => {\n const range = sectorBearingRange(index, sectorCount)\n return {\n ...range,\n clipPath: sectorClipPath(INNER_RADIUS, OUTER_RADIUS, range.startBearing, range.endBearing),\n color: resolveSectorColor({ colorForBearing, colorStops }, (range.startBearing + range.endBearing) / 2, colorAngleValue),\n id: sectorId(index, sectorCount),\n index,\n }\n }),\n [colorAngleValue, colorForBearing, colorStops, sectorCount],\n )\n\n const commitDisabled = useCallback(\n (index: number) => {\n if (disabled) return\n const next = disabledSet.has(index) ? disabledValue.filter((value) => value !== index) : [...disabledValue.filter((value) => value >= 0 && value < sectorCount), index].sort((first, second) => first - second)\n setDisabledValue(next)\n onSectorToggle?.(index, !disabledSet.has(index))\n },\n [disabled, disabledSet, disabledValue, onSectorToggle, sectorCount, setDisabledValue],\n )\n\n // Write the next disabled-sector set out through the controlled/uncontrolled channel.\n const commitDisabledSet = useCallback(\n (next: Set<number>) => {\n setDisabledValue([...next].filter((value) => value >= 0 && value < sectorCount).sort((first, second) => first - second))\n },\n [sectorCount, setDisabledValue],\n )\n\n // Set a single sector's disabled state (idempotent — only commits/reports a real change). Backs\n // both the keyboard Shift+Arrow brush and the pointer brush.\n const setSectorDisabled = useCallback(\n (index: number, nextDisabled: boolean) => {\n if (disabled || disabledSet.has(index) === nextDisabled) return\n const next = new Set(disabledSet)\n if (nextDisabled) next.add(index)\n else next.delete(index)\n commitDisabledSet(next)\n onSectorToggle?.(index, nextDisabled)\n },\n [commitDisabledSet, disabled, disabledSet, onSectorToggle],\n )\n\n // Paint a single sector to the active brush's target state. Marks it as swept (for the section\n // highlight) regardless, but only commits/reports when its state actually changes.\n const paintBrushSector = useCallback(\n (brush: NonNullable<typeof brushRef.current>, index: number) => {\n brush.brushed.add(index)\n if (brush.disabled.has(index) === brush.targetDisabled) return\n if (brush.targetDisabled) brush.disabled.add(index)\n else brush.disabled.delete(index)\n commitDisabledSet(brush.disabled)\n onSectorToggle?.(index, brush.targetDisabled)\n },\n [commitDisabledSet, onSectorToggle],\n )\n\n // Shift+drag brush: the first wedge toggles, and every wedge the pointer sweeps over is painted to\n // *that* wedge's new state (one consistent direction), so a single stroke hides/shows a run of\n // sectors. The pointer is captured and each move fills in every sector between the last sample and\n // the current bearing — geometric, so a fast drag never skips a wedge.\n const startBrush = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>, index: number, rect: DOMRect) => {\n if (disabled) return\n try {\n event.currentTarget.setPointerCapture(event.pointerId)\n } catch {\n // setPointerCapture can throw without an active pointer (e.g. synthetic test events).\n }\n const targetDisabled = !disabledSet.has(index)\n const working = new Set(disabledSet)\n if (targetDisabled) working.add(index)\n else working.delete(index)\n brushRef.current = { brushed: new Set([index]), disabled: working, lastBearing: pointerBearing(event.clientX, event.clientY, rect), pointerId: event.pointerId, rect, targetDisabled }\n // Swallow the trailing click so a shift-click that paints one wedge isn't toggled straight back.\n suppressClickRef.current = true\n setBrushing(true)\n setBrushedIndices([index])\n commitDisabledSet(working)\n onSectorToggle?.(index, targetDisabled)\n },\n [commitDisabledSet, disabled, disabledSet, onSectorToggle],\n )\n\n const endBrush = useCallback(() => {\n if (brushRef.current === null) return\n brushRef.current = null\n setBrushing(false)\n // Drop the stroke's section highlight + the focus accent on the start wedge, so once the stroke\n // is done the dimming (uniform across the run) is the only mark — no single wedge singled out.\n setBrushedIndices([])\n setFocusedSectorIndex(null)\n }, [])\n\n const handleSectorPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>, index: number) => {\n // Reset so a prior drag/brush never swallows this interaction's click.\n suppressClickRef.current = false\n const rect = rootRef.current?.getBoundingClientRect()\n if (!rect) return\n // Shift+drag paints sectors (multi-sector brush) instead of spinning the colour ring.\n if (event.shiftKey) {\n startBrush(event, index, rect)\n return\n }\n if (!canRotate) return\n try {\n event.currentTarget.setPointerCapture(event.pointerId)\n } catch {\n // setPointerCapture can throw without an active pointer (e.g. synthetic test events).\n }\n rotationRef.current = { angle: colorAngleValue, lastBearing: pointerBearing(event.clientX, event.clientY, rect), moved: false, pointerId: event.pointerId, rect, startX: event.clientX, startY: event.clientY, wedge: event.currentTarget }\n },\n [canRotate, colorAngleValue, startBrush],\n )\n\n const handleRingPointerMove = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>) => {\n const brush = brushRef.current\n if (brush !== null) {\n if (brush.pointerId !== event.pointerId) return\n const bearingNow = pointerBearing(event.clientX, event.clientY, brush.rect)\n // The gesture is locked to \"brush\" at pointer-down (Shift was held then); the whole drag\n // paints, so we don't re-check `event.shiftKey` per move — releasing Shift mid-drag would\n // otherwise silently stop the stroke after the first wedge.\n // Step from the last sampled sector to the current one in the direction of travel, painting\n // each — so even a sample that jumps several sectors fills every wedge in between.\n let delta = bearingNow - brush.lastBearing\n if (delta > 180) delta -= 360\n if (delta < -180) delta += 360\n const step = delta >= 0 ? 1 : -1\n const toIndex = sectorIndexForBearing(bearingNow, sectorCount)\n let index = sectorIndexForBearing(brush.lastBearing, sectorCount)\n while (index !== toIndex) {\n index = (index + step + sectorCount) % sectorCount\n paintBrushSector(brush, index)\n }\n brush.lastBearing = bearingNow\n setBrushedIndices([...brush.brushed])\n return\n }\n const drag = rotationRef.current\n if (drag === null || drag.pointerId !== event.pointerId) return\n // Cached rect (the wheel doesn't move mid-drag) avoids a layout read per move.\n const bearingNow = pointerBearing(event.clientX, event.clientY, drag.rect)\n // Signed, wrap-safe step from the last sample (supports multi-turn spins).\n let step = bearingNow - drag.lastBearing\n if (step > 180) step -= 360\n if (step < -180) step += 360\n drag.lastBearing = bearingNow\n drag.angle = normalizeBearing(drag.angle + step)\n if (!drag.moved) {\n if (Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) <= ROTATE_SLOP_PX) return\n drag.moved = true\n suppressClickRef.current = true\n setRotatingRing(true)\n // Drop the wedge focus so no accent ring lingers on a sector while the ring spins.\n drag.wedge.blur()\n }\n setColorAngleValue(drag.angle)\n },\n [paintBrushSector, sectorCount, setColorAngleValue],\n )\n\n const endRingRotation = useCallback(() => {\n rotationRef.current = null\n setRotatingRing(false)\n }, [])\n\n const focusSector = useCallback((index: number) => {\n setActiveSectorIndex(index)\n wedgeRefs.current[index]?.focus()\n }, [])\n\n const handleWedgeKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLButtonElement>, index: number) => {\n if (disabled) return\n // Keyboard brush: holding Shift while arrowing carries the focused wedge's state onto each\n // wedge stepped into (the keyboard analogue of shift+drag) — Space/Enter sets the anchor's\n // state first, then Shift+Arrow paints a run to match it.\n const moveFocus = (next: number) => {\n if (event.shiftKey) setSectorDisabled(next, disabledSet.has(index))\n focusSector(next)\n }\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown': {\n event.preventDefault()\n moveFocus((index + 1) % sectorCount)\n break\n }\n case 'ArrowLeft':\n case 'ArrowUp': {\n event.preventDefault()\n moveFocus((index - 1 + sectorCount) % sectorCount)\n break\n }\n case 'Home': {\n event.preventDefault()\n focusSector(0)\n break\n }\n case 'End': {\n event.preventDefault()\n focusSector(sectorCount - 1)\n break\n }\n default: {\n break\n }\n }\n },\n [disabled, disabledSet, focusSector, sectorCount, setSectorDisabled],\n )\n\n const setThresholdClamped = useCallback(\n (next: number) => {\n setThresholdValue(clampUnit(next))\n },\n [setThresholdValue],\n )\n\n const handleThresholdKeyDown = useCallback(\n (event: ReactKeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowUp': {\n event.preventDefault()\n setThresholdClamped(thresholdValue + thresholdStep)\n break\n }\n case 'ArrowLeft':\n case 'ArrowDown': {\n event.preventDefault()\n setThresholdClamped(thresholdValue - thresholdStep)\n break\n }\n case 'PageUp': {\n event.preventDefault()\n setThresholdClamped(thresholdValue + thresholdStep * 10)\n break\n }\n case 'PageDown': {\n event.preventDefault()\n setThresholdClamped(thresholdValue - thresholdStep * 10)\n break\n }\n case 'Home': {\n event.preventDefault()\n setThresholdClamped(0)\n break\n }\n case 'End': {\n event.preventDefault()\n setThresholdClamped(1)\n break\n }\n default: {\n break\n }\n }\n },\n [setThresholdClamped, thresholdStep, thresholdValue],\n )\n\n const handleDialPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (disabled) return\n // Suppress the default focus shift (a mousedown on this non-focusable div would otherwise\n // pull focus to the body) so the slider focus below sticks.\n event.preventDefault()\n event.currentTarget.setPointerCapture(event.pointerId)\n setDraggingDial(true)\n // Focus the (hidden) slider so the value is immediately arrow-key adjustable after a click.\n thresholdInputRef.current?.focus()\n // Cache the rect for the drag (the wheel doesn't move mid-drag) to avoid a layout read per move.\n const rect = rootRef.current?.getBoundingClientRect() ?? null\n dialRectRef.current = rect\n if (rect) setThresholdClamped(pointerThreshold(event.clientY, rect))\n },\n [disabled, setThresholdClamped],\n )\n\n const handleDialPointerMove = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n const rect = dialRectRef.current\n if (!draggingDial || !rect) return\n setThresholdClamped(pointerThreshold(event.clientY, rect))\n },\n [draggingDial, setThresholdClamped],\n )\n\n const handleRef = useCallback(\n (node: HTMLDivElement | null) => {\n rootRef.current = node\n if (typeof ref === 'function') {\n ref(node)\n } else if (ref) {\n ref.current = node\n }\n },\n [ref],\n )\n\n // Ring + handle in hub-local % (centre 50,50): the ring's diameter grows with the value; the\n // dot sits at its top. Threshold 0 → diameter 0 (dot at the centre); 1 → the track edge.\n // Clamp for rendering: a controlled `threshold` outside [0,1] must not overflow the ring/dot\n // or desync the native input (which clamps) from the announced aria-valuetext.\n const clampedThreshold = clampUnit(thresholdValue)\n const ringDiameterPercent = clampedThreshold * 100 * THRESHOLD_TRACK_FRACTION\n const dotTopPercent = 50 - clampedThreshold * 50 * THRESHOLD_TRACK_FRACTION\n // Hover fill is for the hovered wedge only — never the focused one, which already shows the\n // accent focus ring. Otherwise a just-toggled wedge (focused + hovered) gets fill + ring, which\n // reads as a doubled outline.\n const hoverHighlightIndex = !rotatingRing && hoveredSectorIndex !== null && hoveredSectorIndex !== focusedSectorIndex ? hoveredSectorIndex : null\n // Accent-ringed wedges: during a brush, the whole swept section (so it reads as one active\n // selection); otherwise just the focused wedge.\n const ringIndices = brushing ? brushedIndices : focusedSectorIndex !== null && !rotatingRing ? [focusedSectorIndex] : []\n // The roving-tabindex anchor must stay valid if sectorCount shrinks below it, or the whole\n // wedge group would drop out of the tab order (no button matching a stale index).\n const safeActiveIndex = Math.min(activeSectorIndex, sectorCount - 1)\n const saturation = thresholdToSaturation(clampedThreshold)\n\n // Read-only needle: shown only when a finite bearing is supplied (consumer-driven, e.g. the feed\n // hover cursor). A slim triangle pointing out to the rim at that compass bearing.\n const hasNeedle = typeof bearing === 'number' && Number.isFinite(bearing)\n const needleBearing = hasNeedle ? normalizeBearing(bearing as number) : 0\n const needleTip = polarToCartesian(CENTER, CENTER, NEEDLE_TIP_RADIUS, needleBearing)\n const needleBaseMid = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, needleBearing)\n const needleBaseLeft = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, needleBearing - NEEDLE_HALF_WIDTH_DEGREES)\n const needleBaseRight = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, needleBearing + NEEDLE_HALF_WIDTH_DEGREES)\n\n return (\n <div\n aria-disabled={disabled || undefined}\n aria-label={accessibleName}\n className={cn('relative inline-grid select-none', disabled && 'pointer-events-none opacity-50', className)}\n data-disabled={disabled || undefined}\n data-slot='directional-color-wheel'\n data-testid={dataTestId}\n ref={handleRef}\n role='group'\n style={{ height: size, width: size }}\n >\n {/* Flat coloured sector wedges (the GRAMS look): clipped, toggleable buttons. The\n coherence→saturation preview is one `saturate()` on the whole group (not per wedge), so the\n ring is a single stacking context the overlay can paint over — disabled wedges are grey, so\n the filter is a no-op on them. */}\n <div\n className='col-start-1 row-start-1 grid size-full'\n data-brushing={brushing || undefined}\n data-rotatable={canRotate || undefined}\n data-rotating={rotatingRing || undefined}\n data-slot='directional-color-wheel-sectors'\n data-testid={`${dataTestId}-sectors`}\n style={{ filter: `saturate(${saturation})` }}\n >\n {sectors.map((sector) => {\n const isEnabled = !disabledSet.has(sector.index)\n return (\n <button\n aria-label={`Bearing sector ${Math.round(sector.startBearing)}° to ${Math.round(sector.endBearing)}°`}\n aria-pressed={isEnabled}\n className={cn(\n 'col-start-1 row-start-1 size-full cursor-pointer outline-none motion-safe:transition-opacity motion-safe:duration-150',\n !isEnabled && 'opacity-25',\n canRotate && 'touch-none',\n canRotate && (rotatingRing ? 'cursor-grabbing' : 'cursor-grab'),\n brushing && 'cursor-crosshair',\n )}\n data-sector-id={sector.id}\n data-slot='directional-color-wheel-sector'\n data-testid={`${dataTestId}-sector-${sector.index}`}\n disabled={disabled}\n key={sector.id}\n onBlur={() => {\n setFocusedSectorIndex((current) => (current === sector.index ? null : current))\n }}\n onClick={() => {\n // Swallow the click after a rotation drag so it doesn't also toggle.\n if (suppressClickRef.current) {\n suppressClickRef.current = false\n return\n }\n commitDisabled(sector.index)\n }}\n onFocus={() => {\n setActiveSectorIndex(sector.index)\n setFocusedSectorIndex(sector.index)\n }}\n onKeyDown={(event) => {\n handleWedgeKeyDown(event, sector.index)\n }}\n onLostPointerCapture={() => {\n endRingRotation()\n endBrush()\n }}\n onPointerDown={(event) => {\n handleSectorPointerDown(event, sector.index)\n }}\n onPointerEnter={() => {\n setHoveredSectorIndex(sector.index)\n }}\n onPointerLeave={() => {\n setHoveredSectorIndex((current) => (current === sector.index ? null : current))\n }}\n onPointerMove={handleRingPointerMove}\n onPointerUp={() => {\n endRingRotation()\n endBrush()\n }}\n ref={(node) => {\n wedgeRefs.current[sector.index] = node\n }}\n style={{ background: isEnabled ? sector.color : 'var(--color-level-one)', clipPath: sector.clipPath }}\n tabIndex={disabled || sector.index !== safeActiveIndex ? -1 : 0}\n type='button'\n />\n )\n })}\n </div>\n\n {/* Decorative + interactive overlay, painted above the wedge group (z-10): ring outlines,\n degree bezel, hover/focus highlight, and the compass/degree labels. */}\n <svg\n aria-hidden\n className='pointer-events-none z-10 col-start-1 row-start-1 size-full overflow-visible'\n data-slot='directional-color-wheel-overlay'\n data-testid={`${dataTestId}-overlay`}\n viewBox='0 0 100 100'\n >\n {/* Rings/bezel step up from `border-primary` (neutral-800, nearly invisible) to the mid\n neutrals — `border-secondary` (700) for the rings, `text-disabled` (600) for the minor\n ticks — so the notches read clearly while staying well short of the near-white\n `text-secondary` (300) used for the major ticks + N/E/S/W labels. */}\n <circle\n className='fill-none stroke-border-secondary'\n cx={CENTER}\n cy={CENTER}\n r={OUTER_RADIUS}\n strokeWidth={0.7}\n />\n <circle\n className='fill-none stroke-border-secondary'\n cx={CENTER}\n cy={CENTER}\n r={INNER_RADIUS}\n strokeWidth={0.7}\n />\n <circle\n className='fill-none stroke-border-secondary/60'\n cx={CENTER}\n cy={CENTER}\n r={TICK_OUTER_RADIUS}\n strokeWidth={0.4}\n />\n <g data-testid={`${dataTestId}-degree-ticks`}>\n {DEGREE_TICKS.map((tick) => (\n <line\n className={tick.isMajor ? 'stroke-text-secondary' : 'stroke-text-disabled'}\n key={tick.bearing}\n strokeLinecap='round'\n strokeWidth={tick.isMajor ? 0.8 : 0.5}\n x1={tick.inner.x}\n x2={tick.outer.x}\n y1={tick.inner.y}\n y2={tick.outer.y}\n />\n ))}\n </g>\n {hoverHighlightIndex !== null && sectors[hoverHighlightIndex] ? (\n <path\n className='fill-text-primary/15'\n d={annularSectorPath(CENTER, CENTER, INNER_RADIUS, OUTER_RADIUS, sectors[hoverHighlightIndex].startBearing, sectors[hoverHighlightIndex].endBearing)}\n />\n ) : null}\n {ringIndices.map((ringIndex) =>\n sectors[ringIndex] ? (\n <path\n className='fill-none stroke-accent'\n d={annularSectorPath(CENTER, CENTER, INNER_RADIUS + FOCUS_RADIAL_INSET, OUTER_RADIUS - FOCUS_RADIAL_INSET, sectors[ringIndex].startBearing + FOCUS_ANGULAR_INSET, sectors[ringIndex].endBearing - FOCUS_ANGULAR_INSET)}\n key={ringIndex}\n strokeLinejoin='round'\n strokeWidth={FOCUS_STROKE_WIDTH}\n />\n ) : null,\n )}\n {BEZEL_LABELS.map((label) => (\n <text\n className={label.isCardinal ? 'font-medium fill-text-secondary text-[6px]' : 'fill-text-secondary text-[4px] tabular-nums'}\n dominantBaseline='central'\n key={label.bearing}\n textAnchor='middle'\n x={label.at.x}\n y={label.at.y}\n >\n {label.text}\n </text>\n ))}\n </svg>\n\n {/* Threshold: a gray hub holding a concentric ring whose radius is the value. Drag the red\n handle dot out (or arrow-key the hidden slider) to grow the ring; it shrinks to a point\n and stops at the centre (0). The track edge is 1. */}\n <div\n className={cn('relative z-20 col-start-1 row-start-1 m-auto flex touch-none items-center justify-center rounded-full border border-border-primary shadow-elevation-2', draggingDial ? 'cursor-grabbing' : 'cursor-grab')}\n data-slot='directional-color-wheel-threshold-dial'\n data-testid={`${dataTestId}-threshold-dial`}\n onLostPointerCapture={() => {\n setDraggingDial(false)\n }}\n onPointerDown={handleDialPointerDown}\n onPointerMove={handleDialPointerMove}\n onPointerUp={() => {\n setDraggingDial(false)\n }}\n style={{ background: 'radial-gradient(circle at 38% 30%, var(--color-level-four), var(--color-level-one) 82%)', height: `${INNER_RADIUS * 1.7}%`, width: `${INNER_RADIUS * 1.7}%` }}\n >\n <span\n aria-hidden\n className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-text-secondary/40'\n data-slot='directional-color-wheel-threshold-ring'\n style={{ height: `${ringDiameterPercent}%`, width: `${ringDiameterPercent}%` }}\n />\n <span\n aria-hidden\n className='absolute left-1/2 size-[15%] -translate-x-1/2 -translate-y-1/2 rounded-full border border-border-primary bg-danger-400 shadow-elevation-1'\n data-slot='directional-color-wheel-threshold-indicator'\n data-testid={`${dataTestId}-threshold-indicator`}\n style={{ top: `${dotTopPercent}%` }}\n />\n <input\n aria-label='Coherence threshold'\n aria-valuetext={`${Math.round(clampedThreshold * 100)} percent`}\n className='sr-only'\n data-slot='directional-color-wheel-threshold-input'\n data-testid={`${dataTestId}-threshold`}\n disabled={disabled}\n max={1}\n min={0}\n onBlur={() => {\n setDialFocused(false)\n }}\n onChange={(event) => {\n setThresholdClamped(Number(event.target.value))\n }}\n onFocus={() => {\n setDialFocused(true)\n }}\n onKeyDown={handleThresholdKeyDown}\n ref={thresholdInputRef}\n step={thresholdStep}\n tabIndex={disabled ? -1 : 0}\n type='range'\n value={clampedThreshold}\n />\n </div>\n\n {/* Top overlay above the hub (z-30): the threshold focus ring (an SVG circle matching the\n sector focus ring's stroke width/units exactly) and the read-only bearing needle. */}\n {dialFocused || hasNeedle ? (\n <svg\n aria-hidden\n className='pointer-events-none z-30 col-start-1 row-start-1 size-full overflow-visible'\n viewBox='0 0 100 100'\n >\n {hasNeedle ? (\n <defs>\n <filter\n filterUnits='userSpaceOnUse'\n height='100'\n id={needleShadowId}\n width='100'\n x='0'\n y='0'\n >\n <feDropShadow\n dx='0'\n dy='0.3'\n floodColor='var(--color-level-one)'\n floodOpacity='0.55'\n stdDeviation='0.5'\n />\n </filter>\n </defs>\n ) : null}\n {dialFocused ? (\n <circle\n className='fill-none stroke-accent'\n cx={CENTER}\n cy={CENTER}\n r={THRESHOLD_FOCUS_RADIUS}\n strokeWidth={FOCUS_STROKE_WIDTH}\n />\n ) : null}\n {hasNeedle ? (\n <g\n data-testid={`${dataTestId}-needle`}\n filter={`url(#${needleShadowId})`}\n >\n {/* Read-only bearing needle (consumer-driven; HZN-2658 follows the cursor). Two facets\n split along the centreline give it a lit/shadowed compass look; the drop-shadow\n lifts it off any wedge colour. Decorative — the consumer surfaces the numeric\n bearing in the hover label. */}\n <polygon\n className='fill-danger-500'\n points={`${needleTip.x},${needleTip.y} ${needleBaseLeft.x},${needleBaseLeft.y} ${needleBaseMid.x},${needleBaseMid.y}`}\n />\n <polygon\n className='fill-danger-400'\n points={`${needleTip.x},${needleTip.y} ${needleBaseMid.x},${needleBaseMid.y} ${needleBaseRight.x},${needleBaseRight.y}`}\n />\n </g>\n ) : null}\n </svg>\n ) : null}\n\n {/* The needle SVG is decorative (aria-hidden), so expose the bearing as text for assistive tech. */}\n {hasNeedle ? (\n <span\n className='sr-only'\n data-testid={`${dataTestId}-bearing-value`}\n >\n Bearing {Math.round(needleBearing)} degrees\n </span>\n ) : null}\n </div>\n )\n}\n\nDirectionalColorWheel.displayName = 'DirectionalColorWheel'\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ import { DateTimePicker, DateTimePickerProps } from "./DateTimePicker.js";
18
18
  import { Dialog } from "./Dialog.js";
19
19
  import { DEFAULT_DIRECTIONAL_COLOR_STOPS, DirectionalColorStop, DirectionalSectorCount, SUPPORTED_SECTOR_COUNTS, bearingFromComponents, bearingToPalettePosition, normalizeBearing, sectorBearingRange, sectorId } from "./DirectionalColorWheel/directionalColorWheelMath.js";
20
20
  import { DirectionalColorWheel, DirectionalColorWheelProps } from "./DirectionalColorWheel.js";
21
- import { DirectionalColorWheelDisclosure, DirectionalColorWheelDisclosureProps } from "./DirectionalColorWheel/DirectionalColorWheelDisclosure.js";
21
+ import { DirectionalColorWheelDisclosure, DirectionalColorWheelDisclosurePosition, DirectionalColorWheelDisclosureProps } from "./DirectionalColorWheel/DirectionalColorWheelDisclosure.js";
22
22
  import { DirectionalColorWheelGlyph, DirectionalColorWheelGlyphProps } from "./DirectionalColorWheel/DirectionalColorWheelGlyph.js";
23
23
  import { Drawer, DrawerProps } from "./Drawer.js";
24
24
  import { HoverCard, HoverCardContent, HoverCardProps, HoverCardTrigger } from "./HoverCard.js";
@@ -141,4 +141,4 @@ import { useControllableState } from "./hooks/useControllableState.js";
141
141
  import { useUncontrolledState } from "./hooks/useUncontrolledState.js";
142
142
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea } from "./primitives/input-group.js";
143
143
  import { cn } from "./utils/twUtils.js";
144
- export { Accordion, type AccordionProps, AdjustmentsIcon, Alert, AlertDialog, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogPopup, type AlertDialogPopupProps, type AlertDialogProps, AlertDialogTitle, AlertDialogTrigger, type AlertDialogTriggerProps, type AlertProps, AnalyzeIcon, AnnotationsIcon, ApprovedIcon, ArrowDownIcon, ArrowUpIcon, Avatar, type AvatarProps, Badge, type BadgeProps, BoxToolIcon, Button, ButtonGroup, ButtonGroupItem, type ButtonGroupProps, ButtonGroupSeparator, ButtonIcon, type ButtonIconProps, ButtonIconSlideout, type ButtonIconSlideoutProps, type ButtonProps, CalendarIcon, CheckCircleIcon, CheckSquareIcon, Checkbox, type CheckboxProps, CheckmarkIcon, ChevronDownIcon, ChevronUpIcon, ClockIcon, CloseCircleIcon, CloseIcon, Combobox, type ComboboxOption, type ComboboxProps, ControlGroupSelect, type ControlGroupSelectCaptionLayout, type ControlGroupSelectProps, Crosshairs2Icon, CrosshairsIcon, DEFAULT_DIRECTIONAL_COLOR_STOPS, DashboardIcon, DataCard, type DataCardProps, DatabaseIcon, DateTimePicker, type DateTimePickerProps, DeleteIcon, Dialog, type DirectionalColorStop, DirectionalColorWheel, DirectionalColorWheelDisclosure, type DirectionalColorWheelDisclosureProps, DirectionalColorWheelGlyph, type DirectionalColorWheelGlyphProps, type DirectionalColorWheelProps, type DirectionalSectorCount, Drawer, type DrawerProps, DurationIcon, EditIcon, EmailIcon, EraserIcon, ErrorIcon, ErrorMessage, EyeClosedIcon, EyeClosedIcon2, EyeOpenIcon, FileDownloadIcon, type FormFieldMessageValue, GoToFirstIcon, GoToLastIcon, HarmonicCursorsIcon, HoverCard, HoverCardContent, type HoverCardProps, HoverCardTrigger, IconBase, InfoIcon, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputNumeric, type InputNumericProps, InputOTP, type InputOTPBaseProps, type InputOTPProps, type InputProps, InputSearch, type InputSearchOption, type InputSearchProps, IterationArrowIcon, Kbd, KbdGroup, type KbdGroupProps, type KbdProps, type KbdSymbol, KeyboardIcon, Label, LabelIcon, type LabelProps, LassoIcon, LineToolIcon, LinkIcon, LiveViewIcon, LoaderIcon, LocationIcon, LogoutIcon, MaximizeIcon, MeasureIcon, MenuDotsIcon, MenuIcon, MessagesIcon, MetadataIcon, MinimizeIcon, MinusIcon, MultiSelect, type MultiSelectProps, OntologyIcon, PanelIconClose, PanelIconOpen, PauseIcon, PdfIcon, PlayIcon, PlusIcon, PolygonIcon, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, PrinterIcon, ProgressArrowIcon, ProgressCheckIcon, RadialMenu, type RadialMenuItem, type RadialMenuProps, RadioButton, RadioButtonGroup, RadioButtonGroupItem, type RadioButtonGroupItemProps, type RadioButtonGroupProps, type RadioButtonProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, ResetIcon, ReviewedIcon, SUPPORTED_SECTOR_COUNTS, ScissorsIcon, SearchIcon, Select, type SelectExtendedProps, type SelectProps, Separator, type SeparatorProps, SettingsIcon, Skeleton, Slider, type SliderProps, SortAscendingIcon, SortAtoZIcon, SortDescendingIcon, SortZtoAIcon, SparklesIcon, SpectralProvider, type SpectralProviderProps, StackIcon, StarIcon, SvgIdContext, Switch, type SwitchProps, SyncIcon, SyncOffIcon, Tabs, type TabsProps, Textarea, type TextareaProps, Toast, type ToastProps, Toggle, ToggleGroup, ToggleGroupItem, type ToggleGroupItemProps, type ToggleGroupProps, ToggleGroupSplitMenuItem, type ToggleGroupSplitMenuItemProps, type ToggleProps, Tooltip, TooltipContent, TooltipTrigger, TrashIcon, Tray, type TrayBaseProps, type TrayBodyProps, type TrayContentProps, UndoIcon, UnlinkIcon, UploadIcon, User2Icon, UserIcon, WarningIcon, WarningMessage, ZoomAllIcon, ZoomXIcon, ZoomYIcon, bearingFromComponents, bearingToPalettePosition, cn, normalizeBearing, sectorBearingRange, sectorId, toast, useAccordionAutoScroll, useControllableState, useUncontrolledState };
144
+ export { Accordion, type AccordionProps, AdjustmentsIcon, Alert, AlertDialog, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogPopup, type AlertDialogPopupProps, type AlertDialogProps, AlertDialogTitle, AlertDialogTrigger, type AlertDialogTriggerProps, type AlertProps, AnalyzeIcon, AnnotationsIcon, ApprovedIcon, ArrowDownIcon, ArrowUpIcon, Avatar, type AvatarProps, Badge, type BadgeProps, BoxToolIcon, Button, ButtonGroup, ButtonGroupItem, type ButtonGroupProps, ButtonGroupSeparator, ButtonIcon, type ButtonIconProps, ButtonIconSlideout, type ButtonIconSlideoutProps, type ButtonProps, CalendarIcon, CheckCircleIcon, CheckSquareIcon, Checkbox, type CheckboxProps, CheckmarkIcon, ChevronDownIcon, ChevronUpIcon, ClockIcon, CloseCircleIcon, CloseIcon, Combobox, type ComboboxOption, type ComboboxProps, ControlGroupSelect, type ControlGroupSelectCaptionLayout, type ControlGroupSelectProps, Crosshairs2Icon, CrosshairsIcon, DEFAULT_DIRECTIONAL_COLOR_STOPS, DashboardIcon, DataCard, type DataCardProps, DatabaseIcon, DateTimePicker, type DateTimePickerProps, DeleteIcon, Dialog, type DirectionalColorStop, DirectionalColorWheel, DirectionalColorWheelDisclosure, type DirectionalColorWheelDisclosurePosition, type DirectionalColorWheelDisclosureProps, DirectionalColorWheelGlyph, type DirectionalColorWheelGlyphProps, type DirectionalColorWheelProps, type DirectionalSectorCount, Drawer, type DrawerProps, DurationIcon, EditIcon, EmailIcon, EraserIcon, ErrorIcon, ErrorMessage, EyeClosedIcon, EyeClosedIcon2, EyeOpenIcon, FileDownloadIcon, type FormFieldMessageValue, GoToFirstIcon, GoToLastIcon, HarmonicCursorsIcon, HoverCard, HoverCardContent, type HoverCardProps, HoverCardTrigger, IconBase, InfoIcon, Input, InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, InputNumeric, type InputNumericProps, InputOTP, type InputOTPBaseProps, type InputOTPProps, type InputProps, InputSearch, type InputSearchOption, type InputSearchProps, IterationArrowIcon, Kbd, KbdGroup, type KbdGroupProps, type KbdProps, type KbdSymbol, KeyboardIcon, Label, LabelIcon, type LabelProps, LassoIcon, LineToolIcon, LinkIcon, LiveViewIcon, LoaderIcon, LocationIcon, LogoutIcon, MaximizeIcon, MeasureIcon, MenuDotsIcon, MenuIcon, MessagesIcon, MetadataIcon, MinimizeIcon, MinusIcon, MultiSelect, type MultiSelectProps, OntologyIcon, PanelIconClose, PanelIconOpen, PauseIcon, PdfIcon, PlayIcon, PlusIcon, PolygonIcon, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, PrinterIcon, ProgressArrowIcon, ProgressCheckIcon, RadialMenu, type RadialMenuItem, type RadialMenuProps, RadioButton, RadioButtonGroup, RadioButtonGroupItem, type RadioButtonGroupItemProps, type RadioButtonGroupProps, type RadioButtonProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, ResetIcon, ReviewedIcon, SUPPORTED_SECTOR_COUNTS, ScissorsIcon, SearchIcon, Select, type SelectExtendedProps, type SelectProps, Separator, type SeparatorProps, SettingsIcon, Skeleton, Slider, type SliderProps, SortAscendingIcon, SortAtoZIcon, SortDescendingIcon, SortZtoAIcon, SparklesIcon, SpectralProvider, type SpectralProviderProps, StackIcon, StarIcon, SvgIdContext, Switch, type SwitchProps, SyncIcon, SyncOffIcon, Tabs, type TabsProps, Textarea, type TextareaProps, Toast, type ToastProps, Toggle, ToggleGroup, ToggleGroupItem, type ToggleGroupItemProps, type ToggleGroupProps, ToggleGroupSplitMenuItem, type ToggleGroupSplitMenuItemProps, type ToggleProps, Tooltip, TooltipContent, TooltipTrigger, TrashIcon, Tray, type TrayBaseProps, type TrayBodyProps, type TrayContentProps, UndoIcon, UnlinkIcon, UploadIcon, User2Icon, UserIcon, WarningIcon, WarningMessage, ZoomAllIcon, ZoomXIcon, ZoomYIcon, bearingFromComponents, bearingToPalettePosition, cn, normalizeBearing, sectorBearingRange, sectorId, toast, useAccordionAutoScroll, useControllableState, useUncontrolledState };