@spear-ai/spectral 1.17.10 → 1.18.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.
package/dist/Dialog.js CHANGED
@@ -52,7 +52,7 @@ const DialogContent = ({ children, className, dialogOverlay = true, onEscapeKeyD
52
52
  "data-slot": "dialog-portal",
53
53
  "data-testid": "spectral-dialog-portal",
54
54
  children: [dialogOverlay && /* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
55
- className: cn("max-w-xl gap-4 rounded-lg p-6 shadow-lg has-[[data-slot=dialog-footer]]:pb-0 fixed top-[50%] left-[50%] z-50 flex max-h-[90vh] w-full -translate-x-1/2 -translate-y-1/2 flex-col overflow-y-auto overscroll-contain bg-dialog-bg", "motion-safe:data-[state=open]:duration-200 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:slide-in-from-bottom-20 motion-safe:data-[state=open]:zoom-in-100!", "motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95", RemoveScroll.classNames.fullWidth, className),
55
+ className: cn("max-w-xl gap-4 rounded-lg p-6 shadow-elevation-3 has-[[data-slot=dialog-footer]]:pb-0 fixed top-[50%] left-[50%] z-50 flex max-h-[90vh] w-full -translate-x-1/2 -translate-y-1/2 flex-col overflow-y-auto overscroll-contain bg-dialog-bg", "motion-safe:data-[state=open]:duration-200 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:slide-in-from-bottom-20 motion-safe:data-[state=open]:zoom-in-100!", "motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95", RemoveScroll.classNames.fullWidth, className),
56
56
  "data-slot": "dialog-content",
57
57
  "data-testid": "spectral-dialog-content",
58
58
  onEscapeKeyDown,
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.js","names":[],"sources":["../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import { CloseIcon } from '@components/Icons/CloseIcon'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { cn } from '@utils/twUtils'\nimport { isValidElement, type ComponentProps, type ReactNode } from 'react'\nimport { RemoveScroll } from 'react-remove-scroll'\n\nconst Dialog = ({\n isOpen,\n modal = false,\n ...props\n}: Omit<ComponentProps<typeof DialogPrimitive.Root>, 'open'> & {\n isOpen?: boolean\n dialogOverlay?: boolean\n}) => {\n return <DialogPrimitive.Root open={isOpen} modal={modal} data-slot='dialog' data-testid='spectral-dialog' {...props} />\n}\n\nconst DialogTrigger = ({ ...props }: ComponentProps<typeof DialogPrimitive.Trigger>) => {\n return <DialogPrimitive.Trigger asChild data-testid='spectral-dialog-trigger' {...props} />\n}\n\nconst DialogPortal = ({ ...props }: ComponentProps<typeof DialogPrimitive.Portal>) => {\n return <DialogPrimitive.Portal data-slot='dialog-portal' data-testid='dialog-portal' {...props} />\n}\n\nconst DialogClose = ({ className, ...props }: ComponentProps<typeof DialogPrimitive.Close>) => {\n return <DialogPrimitive.Close asChild data-slot='dialog-close' data-testid='spectral-dialog-close' {...props} className={cn('hover:cursor-pointer', className)} />\n}\n\nconst DialogOverlay = ({ className, ...props }: ComponentProps<typeof DialogPrimitive.Overlay>) => {\n return (\n <DialogPrimitive.Overlay\n className={cn('inset-0 bg-black/50 backdrop-blur-sm fixed z-50 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:animate-out',\n 'motion-safe:data-[state=closed]:fade-out-0', className)}\n data-slot='dialog-overlay'\n data-testid='spectral-dialog-overlay'\n {...props}\n />\n )\n}\n\nconst DialogContent = ({\n children,\n className,\n dialogOverlay = true,\n onEscapeKeyDown,\n onInteractOutside,\n onPointerDownOutside,\n showCloseButton = true,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Content> & {\n dialogOverlay?: boolean\n onEscapeKeyDown?: (event: KeyboardEvent) => void\n onInteractOutside?: (event: PointerEvent | FocusEvent) => void\n onPointerDownOutside?: (event: PointerEvent) => void\n showCloseButton?: boolean\n}) => {\n return (\n <DialogPortal data-slot='dialog-portal' data-testid='spectral-dialog-portal'>\n {dialogOverlay && <DialogOverlay />}\n <DialogPrimitive.Content\n className={cn(\n 'max-w-xl gap-4 rounded-lg p-6 shadow-lg has-[[data-slot=dialog-footer]]:pb-0 fixed top-[50%] left-[50%] z-50 flex max-h-[90vh] w-full -translate-x-1/2 -translate-y-1/2 flex-col overflow-y-auto overscroll-contain bg-dialog-bg',\n 'motion-safe:data-[state=open]:duration-200 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:slide-in-from-bottom-20 motion-safe:data-[state=open]:zoom-in-100!',\n 'motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95',\n RemoveScroll.classNames.fullWidth,\n className,\n )}\n data-slot='dialog-content'\n data-testid='spectral-dialog-content'\n onEscapeKeyDown={onEscapeKeyDown}\n onInteractOutside={onInteractOutside}\n onPointerDownOutside={onPointerDownOutside}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className={`focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-accent data-[state=open]:text-text-primary top-4 right-4 rounded-xs absolute opacity-70 transition-opacity hover:opacity-100 hover:cursor-pointer disabled:pointer-events-none data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:shrink-0`}\n data-slot='dialog-close'\n data-testid='spectral-dialog-close'\n >\n <CloseIcon size={18} />\n <span className='sr-only'>Close dialog</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n )\n}\n\nconst DialogHeader = ({ className, ...props }: ComponentProps<'div'>) => {\n return <div className={cn('gap-2 sm:text-left flex flex-col text-center', className)} data-slot='dialog-header' data-testid='spectral-dialog-header' {...props} />\n}\n\nconst DialogFooter = ({ className, ...props }: ComponentProps<'div'>) => {\n return <div className={cn('gap-2 sm:flex-row sm:justify-end bottom-0 py-4 px-6 -mx-6 sticky z-10 flex flex-col-reverse bg-dialog-bg/85', className)} data-slot='dialog-footer' data-testid='spectral-dialog-footer' {...props} />\n}\n\nconst hasTextContent = (value: ReactNode) => (typeof value === 'string' ? value.trim().length > 0 : typeof value === 'number')\n\nconst DialogTitle = ({ children, className, ...props }: ComponentProps<typeof DialogPrimitive.Title>) => {\n if (hasTextContent(children)) {\n return <DialogPrimitive.Title className={cn('text-2xl font-semibold leading-none', className)} data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n }\n\n if (isValidElement(children)) {\n return <DialogPrimitive.Title asChild data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n }\n\n return <DialogPrimitive.Title className={className} data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n}\n\nconst DialogDescription = ({ children, className, ...props }: ComponentProps<typeof DialogPrimitive.Description>) => {\n if (hasTextContent(children)) {\n return <DialogPrimitive.Description className={cn('text-muted-foreground text-sm', className)} data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n }\n\n if (isValidElement(children)) {\n return <DialogPrimitive.Description asChild data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n }\n\n return <DialogPrimitive.Description className={className} data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n}\n\nexport { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger }\n"],"mappings":"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"}
1
+ {"version":3,"file":"Dialog.js","names":[],"sources":["../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import { CloseIcon } from '@components/Icons/CloseIcon'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { cn } from '@utils/twUtils'\nimport { isValidElement, type ComponentProps, type ReactNode } from 'react'\nimport { RemoveScroll } from 'react-remove-scroll'\n\nconst Dialog = ({\n isOpen,\n modal = false,\n ...props\n}: Omit<ComponentProps<typeof DialogPrimitive.Root>, 'open'> & {\n isOpen?: boolean\n dialogOverlay?: boolean\n}) => {\n return <DialogPrimitive.Root open={isOpen} modal={modal} data-slot='dialog' data-testid='spectral-dialog' {...props} />\n}\n\nconst DialogTrigger = ({ ...props }: ComponentProps<typeof DialogPrimitive.Trigger>) => {\n return <DialogPrimitive.Trigger asChild data-testid='spectral-dialog-trigger' {...props} />\n}\n\nconst DialogPortal = ({ ...props }: ComponentProps<typeof DialogPrimitive.Portal>) => {\n return <DialogPrimitive.Portal data-slot='dialog-portal' data-testid='dialog-portal' {...props} />\n}\n\nconst DialogClose = ({ className, ...props }: ComponentProps<typeof DialogPrimitive.Close>) => {\n return <DialogPrimitive.Close asChild data-slot='dialog-close' data-testid='spectral-dialog-close' {...props} className={cn('hover:cursor-pointer', className)} />\n}\n\nconst DialogOverlay = ({ className, ...props }: ComponentProps<typeof DialogPrimitive.Overlay>) => {\n return (\n <DialogPrimitive.Overlay\n className={cn('inset-0 bg-black/50 backdrop-blur-sm fixed z-50 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:animate-out',\n 'motion-safe:data-[state=closed]:fade-out-0', className)}\n data-slot='dialog-overlay'\n data-testid='spectral-dialog-overlay'\n {...props}\n />\n )\n}\n\nconst DialogContent = ({\n children,\n className,\n dialogOverlay = true,\n onEscapeKeyDown,\n onInteractOutside,\n onPointerDownOutside,\n showCloseButton = true,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Content> & {\n dialogOverlay?: boolean\n onEscapeKeyDown?: (event: KeyboardEvent) => void\n onInteractOutside?: (event: PointerEvent | FocusEvent) => void\n onPointerDownOutside?: (event: PointerEvent) => void\n showCloseButton?: boolean\n}) => {\n return (\n <DialogPortal data-slot='dialog-portal' data-testid='spectral-dialog-portal'>\n {dialogOverlay && <DialogOverlay />}\n <DialogPrimitive.Content\n className={cn(\n 'max-w-xl gap-4 rounded-lg p-6 shadow-elevation-3 has-[[data-slot=dialog-footer]]:pb-0 fixed top-[50%] left-[50%] z-50 flex max-h-[90vh] w-full -translate-x-1/2 -translate-y-1/2 flex-col overflow-y-auto overscroll-contain bg-dialog-bg',\n 'motion-safe:data-[state=open]:duration-200 motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:slide-in-from-bottom-20 motion-safe:data-[state=open]:zoom-in-100!',\n 'motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95',\n RemoveScroll.classNames.fullWidth,\n className,\n )}\n data-slot='dialog-content'\n data-testid='spectral-dialog-content'\n onEscapeKeyDown={onEscapeKeyDown}\n onInteractOutside={onInteractOutside}\n onPointerDownOutside={onPointerDownOutside}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className={`focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-accent data-[state=open]:text-text-primary top-4 right-4 rounded-xs absolute opacity-70 transition-opacity hover:opacity-100 hover:cursor-pointer disabled:pointer-events-none data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:shrink-0`}\n data-slot='dialog-close'\n data-testid='spectral-dialog-close'\n >\n <CloseIcon size={18} />\n <span className='sr-only'>Close dialog</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n )\n}\n\nconst DialogHeader = ({ className, ...props }: ComponentProps<'div'>) => {\n return <div className={cn('gap-2 sm:text-left flex flex-col text-center', className)} data-slot='dialog-header' data-testid='spectral-dialog-header' {...props} />\n}\n\nconst DialogFooter = ({ className, ...props }: ComponentProps<'div'>) => {\n return <div className={cn('gap-2 sm:flex-row sm:justify-end bottom-0 py-4 px-6 -mx-6 sticky z-10 flex flex-col-reverse bg-dialog-bg/85', className)} data-slot='dialog-footer' data-testid='spectral-dialog-footer' {...props} />\n}\n\nconst hasTextContent = (value: ReactNode) => (typeof value === 'string' ? value.trim().length > 0 : typeof value === 'number')\n\nconst DialogTitle = ({ children, className, ...props }: ComponentProps<typeof DialogPrimitive.Title>) => {\n if (hasTextContent(children)) {\n return <DialogPrimitive.Title className={cn('text-2xl font-semibold leading-none', className)} data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n }\n\n if (isValidElement(children)) {\n return <DialogPrimitive.Title asChild data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n }\n\n return <DialogPrimitive.Title className={className} data-slot='dialog-title' data-testid='spectral-dialog-title' {...props}>{children}</DialogPrimitive.Title>\n}\n\nconst DialogDescription = ({ children, className, ...props }: ComponentProps<typeof DialogPrimitive.Description>) => {\n if (hasTextContent(children)) {\n return <DialogPrimitive.Description className={cn('text-muted-foreground text-sm', className)} data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n }\n\n if (isValidElement(children)) {\n return <DialogPrimitive.Description asChild data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n }\n\n return <DialogPrimitive.Description className={className} data-slot='dialog-description' data-testid='spectral-dialog-description' {...props}>{children}</DialogPrimitive.Description>\n}\n\nexport { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger }\n"],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+ import { ReactNode, Ref } from "react";
3
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
4
+
5
+ //#region src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.d.ts
6
+ type DisclosureAlign = 'start' | 'center' | 'end';
7
+ type DisclosureSide = 'top' | 'right' | 'bottom' | 'left';
8
+ interface DirectionalColorWheelDisclosureProps {
9
+ /** Accessible name for the collapsed trigger button (`aria-expanded` conveys the open state). */
10
+ accessibleName?: string;
11
+ /** How the panel aligns to the trigger. Defaults to `start` for a corner-anchored feed widget. */
12
+ align?: DisclosureAlign;
13
+ /** The expandable content: the bare wheel, or the wheel framed by the GRAMS controls. */
14
+ children: ReactNode;
15
+ /** Layout-only class extension for the expanded panel. */
16
+ className?: string;
17
+ /**
18
+ * Override the collapsed affordance. Defaults to a {@link DirectionalColorWheelGlyph} mini
19
+ * compass; pass a palette-matched glyph (or any node) to mirror the expanded wheel's colours.
20
+ */
21
+ collapsedIcon?: ReactNode;
22
+ /** Min distance (px) from the viewport edge before the panel flips/shifts. */
23
+ collisionPadding?: number;
24
+ dataTestId?: string;
25
+ /** Uncontrolled initial expanded state. */
26
+ defaultExpanded?: boolean;
27
+ disabled?: boolean;
28
+ /**
29
+ * Controlled expanded state. `undefined`/`null` leaves the disclosure uncontrolled, so Horizon
30
+ * can gate the feature with a single nullable value (PAT-028).
31
+ */
32
+ expanded?: boolean | null;
33
+ /** Called whenever the disclosure expands or collapses (click, Esc, or outside dismiss). */
34
+ onExpandedChange?: (expanded: boolean) => void;
35
+ ref?: Ref<HTMLButtonElement>;
36
+ /** Which side of the trigger the panel grows toward. Defaults to `top` (icon at a bottom corner). */
37
+ side?: DisclosureSide;
38
+ /** Gap (px) between the trigger and the panel. */
39
+ sideOffset?: number;
40
+ /** Layout-only class extension for the collapsed trigger button. */
41
+ triggerClassName?: string;
42
+ /** Panel width: a pixel number or a CSS width string. Defaults to `'fit-content'` (hugs content). */
43
+ width?: number | string;
44
+ }
45
+ declare function DirectionalColorWheelDisclosure({
46
+ accessibleName,
47
+ align,
48
+ children,
49
+ className,
50
+ collapsedIcon,
51
+ collisionPadding,
52
+ dataTestId,
53
+ defaultExpanded,
54
+ disabled,
55
+ expanded,
56
+ onExpandedChange,
57
+ ref,
58
+ side,
59
+ sideOffset,
60
+ triggerClassName,
61
+ width
62
+ }: DirectionalColorWheelDisclosureProps): _$react_jsx_runtime0.JSX.Element;
63
+ declare namespace DirectionalColorWheelDisclosure {
64
+ var displayName: string;
65
+ }
66
+ //#endregion
67
+ export { DirectionalColorWheelDisclosure, DirectionalColorWheelDisclosureProps };
68
+ //# sourceMappingURL=DirectionalColorWheelDisclosure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DirectionalColorWheelDisclosure.d.ts","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.tsx"],"mappings":";;;;;KAMK,eAAA;AAAA,KACA,cAAA;AAAA,UAEY,oCAAA;EAHZ;EAKH,cAAA;;EAEA,KAAA,GAAQ,eAAA;EAPU;EASlB,QAAA,EAAU,SAAA;EARO;EAUjB,SAAA;EAViB;;AAEnB;;EAaE,aAAA,GAAgB,SAAA;EATR;EAWR,gBAAA;EACA,UAAA;EAWU;EATV,eAAA;EACA,QAAA;EAUqB;;;;EALrB,QAAA;EAlBA;EAoBA,gBAAA,IAAoB,QAAA;EACpB,GAAA,GAAM,GAAA,CAAI,iBAAA;EAdV;EAgBA,IAAA,GAAO,cAAA;EAdP;EAgBA,UAAA;EAbA;EAeA,gBAAA;EATA;EAWA,KAAA;AAAA;AAAA;EAaA,cAAA;EACA,KAAA;EACA,QAAA;EACA,SAAA;EACA,aAAA;EACA,gBAAA;EACA,UAAA;EACA,eAAA;EACA,QAAA;EACA,QAAA;EACA,gBAAA;EACA,GAAA;EACA,IAAA;EACA,UAAA;EACA,gBAAA;EACA;AAAA,GACC,oCAAA,GAAoC,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+ import { cn } from "../utils/twUtils.js";
3
+ import { Card } from "../DataCard/Card.js";
4
+ import { Popover, PopoverContent, PopoverTrigger } from "../Popover.js";
5
+ import { DirectionalColorWheelGlyph } from "./DirectionalColorWheelGlyph.js";
6
+ import { useRef } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+
9
+ //#region src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.tsx
10
+ /**
11
+ * Collapse/expand affordance for the {@link DirectionalColorWheel}, built on the Spectral
12
+ * {@link Popover} primitive so it inherits corner anchoring, click-outside/Esc dismissal, focus
13
+ * management, and `aria-expanded`/`aria-controls` for free. Collapsed it is a small compass glyph
14
+ * (a real `<button>`); expanded it reveals whatever `children` it wraps — the bare wheel, or the
15
+ * wheel plus the GRAMS Color Angle / Threshold / Sectors controls — inside a Spectral {@link Card}
16
+ * panel, growing from the icon with a `transform-origin`-anchored zoom+fade that is automatically
17
+ * skipped under `prefers-reduced-motion`. `className` extends the Card panel.
18
+ */
19
+ const DirectionalColorWheelDisclosure = ({ accessibleName = "Directional color legend", align = "start", children, className, collapsedIcon, collisionPadding = 8, dataTestId = "spectral-directional-color-wheel-disclosure", defaultExpanded, disabled = false, expanded, onExpandedChange, ref, side = "top", sideOffset = 8, triggerClassName, width = "fit-content" }) => {
20
+ const panelRef = useRef(null);
21
+ const panelWidth = typeof width === "number" ? `${width}px` : width;
22
+ return /* @__PURE__ */ jsxs(Popover, {
23
+ defaultOpen: defaultExpanded,
24
+ onOpenChange: onExpandedChange,
25
+ open: expanded ?? void 0,
26
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
27
+ asChild: true,
28
+ children: /* @__PURE__ */ jsx("button", {
29
+ "aria-label": accessibleName,
30
+ className: cn("inline-flex items-center justify-center rounded-full text-text-primary outline-none hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent disabled:pointer-events-none disabled:opacity-50 motion-safe:transition-opacity motion-safe:duration-150", triggerClassName),
31
+ "data-slot": "directional-color-wheel-disclosure-trigger",
32
+ "data-testid": `${dataTestId}-trigger`,
33
+ disabled,
34
+ ref,
35
+ type: "button",
36
+ children: collapsedIcon ?? /* @__PURE__ */ jsx(DirectionalColorWheelGlyph, {})
37
+ })
38
+ }), /* @__PURE__ */ jsx(PopoverContent, {
39
+ align,
40
+ className: "p-0 flex items-center justify-center overflow-visible border-none bg-transparent shadow-none",
41
+ collisionPadding,
42
+ "data-slot": "directional-color-wheel-disclosure-content",
43
+ "data-testid": `${dataTestId}-content`,
44
+ onOpenAutoFocus: (event) => {
45
+ event.preventDefault();
46
+ panelRef.current?.focus();
47
+ },
48
+ side,
49
+ sideOffset,
50
+ width: "fit-content",
51
+ children: /* @__PURE__ */ jsx(Card, {
52
+ className: cn("outline-none", className),
53
+ "data-slot": "directional-color-wheel-disclosure-panel",
54
+ ref: panelRef,
55
+ style: { width: panelWidth },
56
+ tabIndex: -1,
57
+ children
58
+ })
59
+ })]
60
+ });
61
+ };
62
+ DirectionalColorWheelDisclosure.displayName = "DirectionalColorWheelDisclosure";
63
+
64
+ //#endregion
65
+ export { DirectionalColorWheelDisclosure };
66
+ //# sourceMappingURL=DirectionalColorWheelDisclosure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DirectionalColorWheelDisclosure.js","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.tsx"],"sourcesContent":["import { Card } from '@components/DataCard/Card'\nimport { Popover, PopoverContent, PopoverTrigger } from '@components/Popover/Popover'\nimport { cn } from '@utils/twUtils'\nimport { useRef, type ReactNode, type Ref } from 'react'\nimport { DirectionalColorWheelGlyph } from './DirectionalColorWheelGlyph'\n\ntype DisclosureAlign = 'start' | 'center' | 'end'\ntype DisclosureSide = 'top' | 'right' | 'bottom' | 'left'\n\nexport interface DirectionalColorWheelDisclosureProps {\n /** Accessible name for the collapsed trigger button (`aria-expanded` conveys the open state). */\n accessibleName?: string\n /** How the panel aligns to the trigger. Defaults to `start` for a corner-anchored feed widget. */\n align?: DisclosureAlign\n /** The expandable content: the bare wheel, or the wheel framed by the GRAMS controls. */\n children: ReactNode\n /** Layout-only class extension for the expanded panel. */\n className?: string\n /**\n * Override the collapsed affordance. Defaults to a {@link DirectionalColorWheelGlyph} mini\n * compass; pass a palette-matched glyph (or any node) to mirror the expanded wheel's colours.\n */\n collapsedIcon?: ReactNode\n /** Min distance (px) from the viewport edge before the panel flips/shifts. */\n collisionPadding?: number\n dataTestId?: string\n /** Uncontrolled initial expanded state. */\n defaultExpanded?: boolean\n disabled?: boolean\n /**\n * Controlled expanded state. `undefined`/`null` leaves the disclosure uncontrolled, so Horizon\n * can gate the feature with a single nullable value (PAT-028).\n */\n expanded?: boolean | null\n /** Called whenever the disclosure expands or collapses (click, Esc, or outside dismiss). */\n onExpandedChange?: (expanded: boolean) => void\n ref?: Ref<HTMLButtonElement>\n /** Which side of the trigger the panel grows toward. Defaults to `top` (icon at a bottom corner). */\n side?: DisclosureSide\n /** Gap (px) between the trigger and the panel. */\n sideOffset?: number\n /** Layout-only class extension for the collapsed trigger button. */\n triggerClassName?: string\n /** Panel width: a pixel number or a CSS width string. Defaults to `'fit-content'` (hugs content). */\n width?: number | string\n}\n\n/**\n * Collapse/expand affordance for the {@link DirectionalColorWheel}, built on the Spectral\n * {@link Popover} primitive so it inherits corner anchoring, click-outside/Esc dismissal, focus\n * management, and `aria-expanded`/`aria-controls` for free. Collapsed it is a small compass glyph\n * (a real `<button>`); expanded it reveals whatever `children` it wraps — the bare wheel, or the\n * wheel plus the GRAMS Color Angle / Threshold / Sectors controls — inside a Spectral {@link Card}\n * panel, growing from the icon with a `transform-origin`-anchored zoom+fade that is automatically\n * skipped under `prefers-reduced-motion`. `className` extends the Card panel.\n */\nexport const DirectionalColorWheelDisclosure = ({\n accessibleName = 'Directional color legend',\n align = 'start',\n children,\n className,\n collapsedIcon,\n collisionPadding = 8,\n dataTestId = 'spectral-directional-color-wheel-disclosure',\n defaultExpanded,\n disabled = false,\n expanded,\n onExpandedChange,\n ref,\n side = 'top',\n sideOffset = 8,\n triggerClassName,\n width = 'fit-content',\n}: DirectionalColorWheelDisclosureProps) => {\n const panelRef = useRef<HTMLDivElement>(null)\n // `width` sizes the Card panel; the popover hugs it (fit-content) so there is no empty gap.\n const panelWidth = typeof width === 'number' ? `${width}px` : width\n\n return (\n <Popover\n defaultOpen={defaultExpanded}\n onOpenChange={onExpandedChange}\n open={expanded ?? undefined}\n >\n <PopoverTrigger asChild>\n <button\n aria-label={accessibleName}\n className={cn(\n 'inline-flex items-center justify-center rounded-full text-text-primary outline-none hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent disabled:pointer-events-none disabled:opacity-50 motion-safe:transition-opacity motion-safe:duration-150',\n triggerClassName,\n )}\n data-slot='directional-color-wheel-disclosure-trigger'\n data-testid={`${dataTestId}-trigger`}\n disabled={disabled}\n ref={ref}\n type='button'\n >\n {collapsedIcon ?? <DirectionalColorWheelGlyph />}\n </button>\n </PopoverTrigger>\n <PopoverContent\n align={align}\n // Chrome-less positioning/animation layer; the visible panel is the Spectral Card below.\n className='p-0 flex items-center justify-center overflow-visible border-none bg-transparent shadow-none'\n collisionPadding={collisionPadding}\n data-slot='directional-color-wheel-disclosure-content'\n data-testid={`${dataTestId}-content`}\n onOpenAutoFocus={(event) => {\n // Focus the panel, not the first sector wedge (which would paint an accent ring on a sector).\n event.preventDefault()\n panelRef.current?.focus()\n }}\n side={side}\n sideOffset={sideOffset}\n width='fit-content'\n >\n <Card\n className={cn('outline-none', className)}\n data-slot='directional-color-wheel-disclosure-panel'\n ref={panelRef}\n style={{ width: panelWidth }}\n tabIndex={-1}\n >\n {children}\n </Card>\n </PopoverContent>\n </Popover>\n )\n}\n\nDirectionalColorWheelDisclosure.displayName = 'DirectionalColorWheelDisclosure'\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwDA,MAAa,mCAAmC,EAC9C,iBAAiB,4BACjB,QAAQ,SACR,UACA,WACA,eACA,mBAAmB,GACnB,aAAa,+CACb,iBACA,WAAW,OACX,UACA,kBACA,KACA,OAAO,OACP,aAAa,GACb,kBACA,QAAQ,oBACkC;CAC1C,MAAM,WAAW,OAAuB,KAAK;CAE7C,MAAM,aAAa,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM;AAE9D,QACE,qBAAC,SAAD;EACE,aAAa;EACb,cAAc;EACd,MAAM,YAAY;YAHpB,CAKE,oBAAC,gBAAD;GAAgB;aACd,oBAAC,UAAD;IACE,cAAY;IACZ,WAAW,GACT,qSACA,iBACD;IACD,aAAU;IACV,eAAa,GAAG,WAAW;IACjB;IACL;IACL,MAAK;cAEJ,iBAAiB,oBAAC,4BAAD,EAA8B;IACzC;GACM,GACjB,oBAAC,gBAAD;GACS;GAEP,WAAU;GACQ;GAClB,aAAU;GACV,eAAa,GAAG,WAAW;GAC3B,kBAAkB,UAAU;AAE1B,UAAM,gBAAgB;AACtB,aAAS,SAAS,OAAO;;GAErB;GACM;GACZ,OAAM;aAEN,oBAAC,MAAD;IACE,WAAW,GAAG,gBAAgB,UAAU;IACxC,aAAU;IACV,KAAK;IACL,OAAO,EAAE,OAAO,YAAY;IAC5B,UAAU;IAET;IACI;GACQ,EACT;;;AAId,gCAAgC,cAAc"}
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+ import { DirectionalColorStop, DirectionalSectorCount } from "./directionalColorWheelMath.js";
3
+ import { Ref } from "react";
4
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
5
+
6
+ //#region src/components/DirectionalColorWheel/DirectionalColorWheelGlyph.d.ts
7
+ interface DirectionalColorWheelGlyphProps {
8
+ /** Layout-only class extension (margins/padding). */
9
+ className?: string;
10
+ /** "Color Angle": degrees the colours are rotated clockwise, mirroring the full wheel. */
11
+ colorAngle?: number;
12
+ /** Same advanced parity resolver as the wheel; when set it wins over `colorStops`. */
13
+ colorForBearing?: ((bearingDegrees: number) => string) | null;
14
+ /** Palette stops; defaults to the directional DIFAR palette (matches the full wheel). */
15
+ colorStops?: DirectionalColorStop[] | null;
16
+ dataTestId?: string;
17
+ /**
18
+ * Compass bearing (degrees) the miniature needle points at. Defaults to `0` (North) so the
19
+ * glyph always reads as a compass; `null` still renders a North-pointing needle.
20
+ */
21
+ needleBearing?: number | null;
22
+ ref?: Ref<SVGSVGElement>;
23
+ /** Number of coloured wedges drawn in the ring. */
24
+ sectorCount?: DirectionalSectorCount;
25
+ /** Rendered diameter in pixels. */
26
+ size?: number;
27
+ }
28
+ declare function DirectionalColorWheelGlyph({
29
+ className,
30
+ colorAngle,
31
+ colorForBearing,
32
+ colorStops,
33
+ dataTestId,
34
+ needleBearing,
35
+ ref,
36
+ sectorCount,
37
+ size
38
+ }: DirectionalColorWheelGlyphProps): _$react_jsx_runtime0.JSX.Element;
39
+ declare namespace DirectionalColorWheelGlyph {
40
+ var displayName: string;
41
+ }
42
+ //#endregion
43
+ export { DirectionalColorWheelGlyph, DirectionalColorWheelGlyphProps };
44
+ //# sourceMappingURL=DirectionalColorWheelGlyph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DirectionalColorWheelGlyph.d.ts","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelGlyph.tsx"],"mappings":";;;;;;UAIiB,+BAAA;;EAEf,SAAA;EAFe;EAIf,UAAA;;EAEA,eAAA,KAAoB,cAAA;EASV;EAPV,UAAA,GAAa,oBAAA;EACb,UAAA;EAQoC;;;;EAHpC,aAAA;EACA,GAAA,GAAM,GAAA,CAAI,aAAA;EAPV;EASA,WAAA,GAAc,sBAAA;EARd;EAUA,IAAA;AAAA;AAAA;EA0B2C,SAAA;EAAW,UAAA;EAAgB,eAAA;EAAiB,UAAA;EAAY,UAAA;EAAuD,aAAA;EAAe,GAAA;EAAK,WAAA;EAAkB;AAAA,GAAa,+BAAA,GAA+B,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
@@ -0,0 +1,138 @@
1
+ 'use client';
2
+ import { cn } from "../utils/twUtils.js";
3
+ import { annularSectorPath, normalizeBearing, polarToCartesian, resolveSectorColor, sectorBearingRange } from "./directionalColorWheelMath.js";
4
+ import { useId, useMemo } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+
7
+ //#region src/components/DirectionalColorWheel/DirectionalColorWheelGlyph.tsx
8
+ const CENTER = 50;
9
+ const OUTER_RADIUS = 46;
10
+ const INNER_RADIUS = 27;
11
+ const TICK_INNER_RADIUS = 47.5;
12
+ const TICK_OUTER_RADIUS = 49.5;
13
+ const NEEDLE_TIP_RADIUS = 43;
14
+ const NEEDLE_BASE_RADIUS = INNER_RADIUS;
15
+ const NEEDLE_HALF_WIDTH_DEGREES = 8;
16
+ const PIVOT_RADIUS = 5;
17
+ const CARDINAL_TICKS = [
18
+ 0,
19
+ 90,
20
+ 180,
21
+ 270
22
+ ].map((bearing) => ({
23
+ bearing,
24
+ inner: polarToCartesian(CENTER, CENTER, TICK_INNER_RADIUS, bearing),
25
+ outer: polarToCartesian(CENTER, CENTER, TICK_OUTER_RADIUS, bearing)
26
+ }));
27
+ /**
28
+ * Miniature, decorative render of the {@link DirectionalColorWheel} colour ring with a compass
29
+ * needle — the collapsed affordance for {@link DirectionalColorWheelDisclosure}. It reuses the
30
+ * wheel's geometry/colour helpers (and palette props) so it stays a true legend in miniature. It
31
+ * is `aria-hidden`; the surrounding trigger button carries the accessible name.
32
+ */
33
+ const DirectionalColorWheelGlyph = ({ className, colorAngle = 0, colorForBearing, colorStops, dataTestId = "spectral-directional-color-wheel-glyph", needleBearing, ref, sectorCount = 12, size = 28 }) => {
34
+ const hubGradientId = `directional-glyph-hub-${useId().replaceAll(":", "")}`;
35
+ const sectors = useMemo(() => Array.from({ length: sectorCount }, (_, index) => {
36
+ const range = sectorBearingRange(index, sectorCount);
37
+ return {
38
+ color: resolveSectorColor({
39
+ colorForBearing,
40
+ colorStops
41
+ }, (range.startBearing + range.endBearing) / 2, colorAngle),
42
+ key: `${range.startBearing}-${range.endBearing}`,
43
+ path: annularSectorPath(CENTER, CENTER, INNER_RADIUS, OUTER_RADIUS, range.startBearing, range.endBearing)
44
+ };
45
+ }), [
46
+ colorAngle,
47
+ colorForBearing,
48
+ colorStops,
49
+ sectorCount
50
+ ]);
51
+ const bearing = normalizeBearing(typeof needleBearing === "number" && Number.isFinite(needleBearing) ? needleBearing : 0);
52
+ const needleTip = polarToCartesian(CENTER, CENTER, NEEDLE_TIP_RADIUS, bearing);
53
+ const needleBaseMid = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing);
54
+ const needleLeft = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing - NEEDLE_HALF_WIDTH_DEGREES);
55
+ const needleRight = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing + NEEDLE_HALF_WIDTH_DEGREES);
56
+ return /* @__PURE__ */ jsxs("svg", {
57
+ "aria-hidden": true,
58
+ className: cn("shrink-0", className),
59
+ "data-slot": "directional-color-wheel-glyph",
60
+ "data-testid": dataTestId,
61
+ height: size,
62
+ ref,
63
+ viewBox: "0 0 100 100",
64
+ width: size,
65
+ xmlns: "http://www.w3.org/2000/svg",
66
+ children: [
67
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("radialGradient", {
68
+ cx: "38%",
69
+ cy: "30%",
70
+ id: hubGradientId,
71
+ r: "75%",
72
+ children: [/* @__PURE__ */ jsx("stop", {
73
+ offset: "0%",
74
+ stopColor: "var(--color-level-four)"
75
+ }), /* @__PURE__ */ jsx("stop", {
76
+ offset: "82%",
77
+ stopColor: "var(--color-level-one)"
78
+ })]
79
+ }) }),
80
+ /* @__PURE__ */ jsx("g", {
81
+ "data-slot": "directional-color-wheel-glyph-ring",
82
+ children: sectors.map((sector) => /* @__PURE__ */ jsx("path", {
83
+ d: sector.path,
84
+ fill: sector.color
85
+ }, sector.key))
86
+ }),
87
+ /* @__PURE__ */ jsx("circle", {
88
+ className: "fill-none stroke-border-primary",
89
+ cx: CENTER,
90
+ cy: CENTER,
91
+ r: OUTER_RADIUS,
92
+ strokeWidth: 1.5
93
+ }),
94
+ /* @__PURE__ */ jsx("g", {
95
+ "data-slot": "directional-color-wheel-glyph-ticks",
96
+ children: CARDINAL_TICKS.map((tick) => /* @__PURE__ */ jsx("line", {
97
+ className: "stroke-text-secondary",
98
+ strokeLinecap: "round",
99
+ strokeWidth: 1.5,
100
+ x1: tick.inner.x,
101
+ x2: tick.outer.x,
102
+ y1: tick.inner.y,
103
+ y2: tick.outer.y
104
+ }, tick.bearing))
105
+ }),
106
+ /* @__PURE__ */ jsx("circle", {
107
+ className: "stroke-border-primary",
108
+ cx: CENTER,
109
+ cy: CENTER,
110
+ fill: `url(#${hubGradientId})`,
111
+ r: INNER_RADIUS,
112
+ strokeWidth: 1.5
113
+ }),
114
+ /* @__PURE__ */ jsxs("g", {
115
+ "data-slot": "directional-color-wheel-glyph-needle",
116
+ children: [/* @__PURE__ */ jsx("polygon", {
117
+ className: "fill-danger-500",
118
+ points: `${needleTip.x},${needleTip.y} ${needleLeft.x},${needleLeft.y} ${needleBaseMid.x},${needleBaseMid.y}`
119
+ }), /* @__PURE__ */ jsx("polygon", {
120
+ className: "fill-danger-400",
121
+ points: `${needleTip.x},${needleTip.y} ${needleBaseMid.x},${needleBaseMid.y} ${needleRight.x},${needleRight.y}`
122
+ })]
123
+ }),
124
+ /* @__PURE__ */ jsx("circle", {
125
+ className: "fill-level-four stroke-border-primary",
126
+ cx: CENTER,
127
+ cy: CENTER,
128
+ r: PIVOT_RADIUS,
129
+ strokeWidth: 1.5
130
+ })
131
+ ]
132
+ });
133
+ };
134
+ DirectionalColorWheelGlyph.displayName = "DirectionalColorWheelGlyph";
135
+
136
+ //#endregion
137
+ export { DirectionalColorWheelGlyph };
138
+ //# sourceMappingURL=DirectionalColorWheelGlyph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DirectionalColorWheelGlyph.js","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelGlyph.tsx"],"sourcesContent":["import { cn } from '@utils/twUtils'\nimport { useId, useMemo, type Ref } from 'react'\nimport { annularSectorPath, normalizeBearing, polarToCartesian, resolveSectorColor, sectorBearingRange, type DirectionalColorStop, type DirectionalSectorCount } from './directionalColorWheelMath'\n\nexport interface DirectionalColorWheelGlyphProps {\n /** Layout-only class extension (margins/padding). */\n className?: string\n /** \"Color Angle\": degrees the colours are rotated clockwise, mirroring the full wheel. */\n colorAngle?: number\n /** Same advanced parity resolver as the wheel; when set it wins over `colorStops`. */\n colorForBearing?: ((bearingDegrees: number) => string) | null\n /** Palette stops; defaults to the directional DIFAR palette (matches the full wheel). */\n colorStops?: DirectionalColorStop[] | null\n dataTestId?: string\n /**\n * Compass bearing (degrees) the miniature needle points at. Defaults to `0` (North) so the\n * glyph always reads as a compass; `null` still renders a North-pointing needle.\n */\n needleBearing?: number | null\n ref?: Ref<SVGSVGElement>\n /** Number of coloured wedges drawn in the ring. */\n sectorCount?: DirectionalSectorCount\n /** Rendered diameter in pixels. */\n size?: number\n}\n\n// 0..100 viewBox mirroring the wheel: coloured ring, cardinal ticks, gradient hub, two-facet needle.\nconst CENTER = 50\nconst OUTER_RADIUS = 46\nconst INNER_RADIUS = 27\nconst TICK_INNER_RADIUS = 47.5\nconst TICK_OUTER_RADIUS = 49.5\nconst NEEDLE_TIP_RADIUS = 43\nconst NEEDLE_BASE_RADIUS = INNER_RADIUS\nconst NEEDLE_HALF_WIDTH_DEGREES = 8\nconst PIVOT_RADIUS = 5\n// Short rim ticks at N/E/S/W orient the glyph as a compass without crowding it with labels.\nconst CARDINAL_TICKS = [0, 90, 180, 270].map((bearing) => ({\n bearing,\n inner: polarToCartesian(CENTER, CENTER, TICK_INNER_RADIUS, bearing),\n outer: polarToCartesian(CENTER, CENTER, TICK_OUTER_RADIUS, bearing),\n}))\n\n/**\n * Miniature, decorative render of the {@link DirectionalColorWheel} colour ring with a compass\n * needle — the collapsed affordance for {@link DirectionalColorWheelDisclosure}. It reuses the\n * wheel's geometry/colour helpers (and palette props) so it stays a true legend in miniature. It\n * is `aria-hidden`; the surrounding trigger button carries the accessible name.\n */\nexport const DirectionalColorWheelGlyph = ({ className, colorAngle = 0, colorForBearing, colorStops, dataTestId = 'spectral-directional-color-wheel-glyph', needleBearing, ref, sectorCount = 12, size = 28 }: DirectionalColorWheelGlyphProps) => {\n const hubGradientId = `directional-glyph-hub-${useId().replaceAll(':', '')}`\n\n const sectors = useMemo(\n () =>\n Array.from({ length: sectorCount }, (_, index) => {\n const range = sectorBearingRange(index, sectorCount)\n return {\n color: resolveSectorColor({ colorForBearing, colorStops }, (range.startBearing + range.endBearing) / 2, colorAngle),\n key: `${range.startBearing}-${range.endBearing}`,\n path: annularSectorPath(CENTER, CENTER, INNER_RADIUS, OUTER_RADIUS, range.startBearing, range.endBearing),\n }\n }),\n [colorAngle, colorForBearing, colorStops, sectorCount],\n )\n\n const bearing = normalizeBearing(typeof needleBearing === 'number' && Number.isFinite(needleBearing) ? needleBearing : 0)\n const needleTip = polarToCartesian(CENTER, CENTER, NEEDLE_TIP_RADIUS, bearing)\n const needleBaseMid = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing)\n const needleLeft = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing - NEEDLE_HALF_WIDTH_DEGREES)\n const needleRight = polarToCartesian(CENTER, CENTER, NEEDLE_BASE_RADIUS, bearing + NEEDLE_HALF_WIDTH_DEGREES)\n\n return (\n <svg\n aria-hidden\n className={cn('shrink-0', className)}\n data-slot='directional-color-wheel-glyph'\n data-testid={dataTestId}\n height={size}\n ref={ref}\n viewBox='0 0 100 100'\n width={size}\n xmlns='http://www.w3.org/2000/svg'\n >\n <defs>\n <radialGradient\n cx='38%'\n cy='30%'\n id={hubGradientId}\n r='75%'\n >\n <stop\n offset='0%'\n stopColor='var(--color-level-four)'\n />\n <stop\n offset='82%'\n stopColor='var(--color-level-one)'\n />\n </radialGradient>\n </defs>\n <g data-slot='directional-color-wheel-glyph-ring'>\n {sectors.map((sector) => (\n <path\n d={sector.path}\n fill={sector.color}\n key={sector.key}\n />\n ))}\n </g>\n <circle\n className='fill-none stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n r={OUTER_RADIUS}\n strokeWidth={1.5}\n />\n <g data-slot='directional-color-wheel-glyph-ticks'>\n {CARDINAL_TICKS.map((tick) => (\n <line\n className='stroke-text-secondary'\n key={tick.bearing}\n strokeLinecap='round'\n strokeWidth={1.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 <circle\n className='stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n fill={`url(#${hubGradientId})`}\n r={INNER_RADIUS}\n strokeWidth={1.5}\n />\n <g data-slot='directional-color-wheel-glyph-needle'>\n <polygon\n className='fill-danger-500'\n points={`${needleTip.x},${needleTip.y} ${needleLeft.x},${needleLeft.y} ${needleBaseMid.x},${needleBaseMid.y}`}\n />\n <polygon\n className='fill-danger-400'\n points={`${needleTip.x},${needleTip.y} ${needleBaseMid.x},${needleBaseMid.y} ${needleRight.x},${needleRight.y}`}\n />\n </g>\n <circle\n className='fill-level-four stroke-border-primary'\n cx={CENTER}\n cy={CENTER}\n r={PIVOT_RADIUS}\n strokeWidth={1.5}\n />\n </svg>\n )\n}\n\nDirectionalColorWheelGlyph.displayName = 'DirectionalColorWheelGlyph'\n"],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+ //#region src/components/DirectionalColorWheel/directionalColorWheelMath.d.ts
3
+ /**
4
+ * Pure geometry + colour-mapping helpers for {@link DirectionalColorWheel}.
5
+ *
6
+ * The wheel is a *legend* for the directional spectrogram, not an independent
7
+ * colour scheme. Its bearing→colour convention is intentionally identical to the
8
+ * CPU compositor `bufferToDirectionalImageData` (Horizon `tile-data.ts`). Keeping
9
+ * this in a small, dependency-free module lets it be unit-tested in isolation and
10
+ * gives consumers (HZN-2656) a single importable source for the convention. See
11
+ * `DirectionalColorWheel.consistency.md` for the full alignment proof.
12
+ */
13
+ /** Sector counts the wheel supports, matching the GRAMS dialog. */
14
+ declare const SUPPORTED_SECTOR_COUNTS: readonly [4, 8, 12, 16, 20, 24, 32];
15
+ type DirectionalSectorCount = (typeof SUPPORTED_SECTOR_COUNTS)[number];
16
+ /** A palette stop, mirroring the shape of Horizon's `DIRECTIONAL_OKLCH_STOP_LIST`. */
17
+ interface DirectionalColorStop {
18
+ /** Any CSS colour (an `oklch(…)` string keeps parity with the compositor's palette). */
19
+ color: string;
20
+ /** Position along the palette, `0` (bearing 0°) to `1` (bearing 360°). */
21
+ position: number;
22
+ }
23
+ /**
24
+ * Default palette. These are the same OKLCH stops, at the same positions, as the
25
+ * compositor's `DIRECTIONAL_OKLCH_STOP_LIST` — so a wheel rendered with no
26
+ * `colorStops`/`colorForBearing` prop already matches the directional feed's
27
+ * default DIFAR palette. Consumers override this with the exact feed palette.
28
+ */
29
+ declare const DEFAULT_DIRECTIONAL_COLOR_STOPS: DirectionalColorStop[];
30
+ /** Wrap any angle into `[0, 360)`. */
31
+ declare const normalizeBearing: (degrees: number) => number;
32
+ /**
33
+ * The compositor's bearing definition, verbatim: `atan2(sine, cosine)` in
34
+ * degrees, shifted into `[0, 360)`. Exposed so consumers can derive the same
35
+ * bearing the feed colours by, instead of re-deriving it. Mirrors
36
+ * `bufferToDirectionalImageData` (`tile-data.ts`).
37
+ */
38
+ declare const bearingFromComponents: (sineValue: number, cosineValue: number) => number;
39
+ /**
40
+ * Map a compass bearing to the palette position whose colour the wheel shows
41
+ * there. With `colorAngle === 0` this is `bearing / 360` — exactly how the
42
+ * compositor indexes its gradient (`(bearingDegrees / 360) * RESOLUTION`).
43
+ * `colorAngle` rotates the colour assignment clockwise around the ring.
44
+ */
45
+ declare const bearingToPalettePosition: (bearing: number, colorAngle: number) => number;
46
+ /** Point on the wheel for a bearing, using the compass convention (0° at top, clockwise). */
47
+ declare const polarToCartesian: (centerX: number, centerY: number, radius: number, bearing: number) => {
48
+ x: number;
49
+ y: number;
50
+ };
51
+ /** Inclusive/exclusive bearing range covered by sector `index` of `count` equal sectors. */
52
+ declare const sectorBearingRange: (index: number, count: number) => {
53
+ endBearing: number;
54
+ startBearing: number;
55
+ };
56
+ /**
57
+ * Stable id for sector `index`, formatted like Horizon's `DEFAULT_DIRECTION_GROUP_LIST`
58
+ * ids (`sector-0-120`) so HZN-2656 can build a `hiddenDirectionGroupList` directly.
59
+ */
60
+ declare const sectorId: (index: number, count: number) => string;
61
+ /** Threshold (coherence, `0..1`) → CSS `saturate()` multiplier. Higher threshold → more desaturated. */
62
+ declare const thresholdToSaturation: (threshold: number) => number;
63
+ /**
64
+ * SVG path (`d`) for an annular sector (donut wedge) spanning `[startBearing, endBearing]`
65
+ * between `innerRadius` and `outerRadius`. Used for the divider/dim/highlight overlays.
66
+ */
67
+ declare const annularSectorPath: (centerX: number, centerY: number, innerRadius: number, outerRadius: number, startBearing: number, endBearing: number) => string;
68
+ /**
69
+ * CSS `clip-path: polygon(…)` for an annular sector, in percentage coordinates of a
70
+ * square box (centre at `50% 50%`). Lets a full-size, transparent `<button>` expose
71
+ * only its wedge as the interactive/hit region.
72
+ */
73
+ declare const sectorClipPath: (innerRadiusPercent: number, outerRadiusPercent: number, startBearing: number, endBearing: number) => string;
74
+ /**
75
+ * Colour of a palette at a normalized position `[0, 1]`, interpolating OKLCH stops with
76
+ * shortest-hue — the same interpolation the compositor's `buildBearingGradient` uses. Falls
77
+ * back to the nearest stop's raw colour for non-OKLCH palettes (e.g. hsl/hex), which can't be
78
+ * safely lerped without a full colour library.
79
+ */
80
+ declare const colorForPalettePosition: (stops: DirectionalColorStop[], position: number) => string;
81
+ /**
82
+ * Flat colour for a sector centred at `centerBearing`, honouring `colorAngle`. Each GRAMS
83
+ * sector is filled with one colour — the palette evaluated at
84
+ * `bearingToPalettePosition(centerBearing, colorAngle)`, i.e. the same colour the compositor
85
+ * would give a pixel whose bearing is the sector's centre.
86
+ */
87
+ declare const resolveSectorColor: (source: {
88
+ colorForBearing?: ((bearing: number) => string) | null;
89
+ colorStops?: DirectionalColorStop[] | null;
90
+ }, centerBearing: number, colorAngle: number) => string;
91
+ //#endregion
92
+ export { DEFAULT_DIRECTIONAL_COLOR_STOPS, DirectionalColorStop, DirectionalSectorCount, SUPPORTED_SECTOR_COUNTS, annularSectorPath, bearingFromComponents, bearingToPalettePosition, colorForPalettePosition, normalizeBearing, polarToCartesian, resolveSectorColor, sectorBearingRange, sectorClipPath, sectorId, thresholdToSaturation };
93
+ //# sourceMappingURL=directionalColorWheelMath.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directionalColorWheelMath.d.ts","names":[],"sources":["../../src/components/DirectionalColorWheel/directionalColorWheelMath.ts"],"mappings":";;;AAYA;;;;;AAEA;;;;;cAFa,uBAAA;AAAA,KAED,sBAAA,WAAiC,uBAAA;;UAG5B,oBAAA;EAIP;EAFR,KAAA;EAoBD;EAlBC,QAAA;AAAA;;AAuBF;;;;;cAda,+BAAA,EAAiC,oBAAA;;cAcjC,gBAAA,GAAgB,OAAA;;;AAsB7B;;;;cAda,qBAAA,GAAqB,SAAA,UAAqB,WAAA;AAiBvD;;;;;;AAAA,cAHa,wBAAA,GAAwB,OAAA,UAAmB,UAAA;;cAG3C,gBAAA,GAAgB,OAAA,UAAmB,OAAA,UAAiB,MAAA,UAAgB,OAAA;EAAsB,CAAA;EAAW,CAAA;AAAA;AASlH;AAAA,cAAa,kBAAA,GAAkB,KAAA,UAAiB,KAAA;EAAoB,UAAA;EAAoB,YAAA;AAAA;;;;;cAS3E,QAAA,GAAQ,KAAA,UAAiB,KAAA;AAAtC;AAAA,cAMa,qBAAA,GAAqB,SAAA;;;;AAAlC;cAQa,iBAAA,GAAiB,OAAA,UAAmB,OAAA,UAAiB,WAAA,UAAqB,WAAA,UAAqB,YAAA,UAAsB,UAAA;;;;AAAlI;;cAqBa,cAAA,GAAc,kBAAA,UAA8B,kBAAA,UAA4B,YAAA,UAAsB,UAAA;;;;;;;cAwC9F,uBAAA,GAAuB,KAAA,EAAW,oBAAA,IAAsB,QAAA;;;AAxCrE;;;;cAmFa,kBAAA,GAAkB,MAAA;EAAc,eAAA,KAAoB,OAAA;EAAoC,UAAA,GAAa,oBAAA;AAAA,GAA+B,aAAA,UAAuB,UAAA"}