@spear-ai/spectral 1.19.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Combobox.js +0 -22
- package/dist/Combobox.js.map +1 -1
- package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.d.ts +35 -2
- package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.d.ts.map +1 -1
- package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.js +92 -3
- package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.js.map +1 -1
- package/dist/DirectionalColorWheel.d.ts.map +1 -1
- package/dist/DirectionalColorWheel.js +130 -21
- package/dist/DirectionalColorWheel.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/styles/spectral.css +1 -1
- package/package.json +1 -1
package/dist/Combobox.js
CHANGED
|
@@ -27,10 +27,6 @@ const Combobox = ({ className, disabled, defaultValue = "", dropdownWidth = "tri
|
|
|
27
27
|
const [open, setOpen] = useState(false);
|
|
28
28
|
const [inputValue, setInputValue] = useState("");
|
|
29
29
|
const inputRef = useRef(null);
|
|
30
|
-
const suppressNextInputFocusOpenRef = useRef(false);
|
|
31
|
-
const clearFocusSuppression = () => {
|
|
32
|
-
suppressNextInputFocusOpenRef.current = false;
|
|
33
|
-
};
|
|
34
30
|
const [value, setValue] = useUncontrolledState({
|
|
35
31
|
value: valueProp,
|
|
36
32
|
defaultValue,
|
|
@@ -117,13 +113,6 @@ const Combobox = ({ className, disabled, defaultValue = "", dropdownWidth = "tri
|
|
|
117
113
|
className: cn("min-w-0 text-base flex-1 truncate overflow-hidden border-0 bg-transparent whitespace-nowrap text-input-text outline-hidden outline-0 placeholder:opacity-100 focus-visible:ring-0 focus-visible:outline-none", showsSelectedValueAsPlaceholder ? "placeholder:text-input-text!" : "placeholder:text-input-text-placeholder!"),
|
|
118
114
|
"data-slot": "input-group-control",
|
|
119
115
|
id: comboboxId,
|
|
120
|
-
onFocus: () => {
|
|
121
|
-
if (suppressNextInputFocusOpenRef.current) {
|
|
122
|
-
suppressNextInputFocusOpenRef.current = false;
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
if (!isDisabled && !isLoading) setOpen(true);
|
|
126
|
-
},
|
|
127
116
|
ref: inputRef,
|
|
128
117
|
placeholder: selectedOption?.label ?? placeholder,
|
|
129
118
|
...ariaProps
|
|
@@ -134,17 +123,6 @@ const Combobox = ({ className, disabled, defaultValue = "", dropdownWidth = "tri
|
|
|
134
123
|
"aria-label": "Toggle options",
|
|
135
124
|
className: "cursor-pointer",
|
|
136
125
|
"data-slot": "combobox-trigger-button",
|
|
137
|
-
onPointerDownCapture: () => {
|
|
138
|
-
const inputElement = inputRef.current;
|
|
139
|
-
if (!inputElement) {
|
|
140
|
-
clearFocusSuppression();
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
suppressNextInputFocusOpenRef.current = inputElement.ownerDocument.activeElement !== inputElement;
|
|
144
|
-
},
|
|
145
|
-
onPointerUpCapture: clearFocusSuppression,
|
|
146
|
-
onPointerCancelCapture: clearFocusSuppression,
|
|
147
|
-
onTouchEndCapture: clearFocusSuppression,
|
|
148
126
|
children: isLoading ? /* @__PURE__ */ jsx(LoaderIcon, { className: "size-5 motion-safe:animate-spin" }) : /* @__PURE__ */ jsx(ChevronDownIcon, { className: cn("size-5 shrink-0 transition-transform duration-200", open && "rotate-180") })
|
|
149
127
|
})
|
|
150
128
|
})]
|
package/dist/Combobox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Combobox.js","names":["ComboboxPrimitive"],"sources":["../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { Combobox as ComboboxPrimitive } from '@base-ui/react'\nimport { CheckmarkIcon, ChevronDownIcon, LoaderIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { InputGroup, InputGroupAddon } from '@primitives/input-group'\nimport {\n EmptyState,\n ErrorMessage,\n getAriaProps,\n getDropdownWidthStyles,\n getDropdownSurfaceClasses,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getOptionClasses,\n getTriggerClasses,\n LoadingState,\n WarningMessage,\n useFormFieldId,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useMemo, useRef, useState, type CSSProperties, type MouseEvent, type Ref } from 'react'\n\nexport type ComboboxOption = BaseOption\n\nexport interface ComboboxProps extends Omit<BaseFormFieldProps, 'onChange' | 'state'> {\n className?: string\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n labelClassName?: string\n loadingMessage?: string\n onChange?: (value: string) => void\n onValueChange?: (value: string) => void\n options: ComboboxOption[]\n placeholder?: string\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nexport const Combobox = ({\n className,\n disabled,\n defaultValue = '',\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found.',\n errorMessage,\n id,\n label,\n labelClassName,\n loadingMessage = 'Loading…',\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onValueChange,\n options,\n placeholder = 'Search…',\n ref,\n required,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n}: ComboboxProps & { ref?: Ref<HTMLDivElement> }) => {\n if (process.env.NODE_ENV !== 'production' && !label && !ariaLabel) {\n console.warn('Combobox: provide either `label` or `aria-label` for an accessible name.')\n }\n\n const comboboxId = useFormFieldId(id, name)\n const listboxId = `${comboboxId}-listbox`\n const errorMessageId = getErrorMessageId(comboboxId)\n const warningMessageId = `${comboboxId}-warning`\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const isDisabled = Boolean(disabled)\n const isLoading = state === 'loading'\n const isInvalid = state === 'error'\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n\n const [open, setOpen] = useState(false)\n const [inputValue, setInputValue] = useState('')\n const inputRef = useRef<HTMLInputElement>(null)\n const suppressNextInputFocusOpenRef = useRef(false)\n const clearFocusSuppression = () => {\n suppressNextInputFocusOpenRef.current = false\n }\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue,\n onChange: (nextValue) => {\n if (onChange) {\n onChange(nextValue)\n } else {\n onValueChange?.(nextValue)\n }\n },\n })\n\n const selectedOption = useMemo(() => options.find((o) => o.value === value) ?? null, [options, value])\n const filteredOptions = useMemo(() => {\n const query = inputValue.trim().toLowerCase()\n if (!query) return options\n return options.filter((option) => option.label.toLowerCase().includes(query))\n }, [inputValue, options])\n const showsSelectedValueAsPlaceholder = Boolean(selectedOption && inputValue.length === 0)\n const { dropdownWidthMode, dropdownWidthStyle } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: 'var(--anchor-width)',\n })\n\n const handleValueChange = (nextOption: ComboboxOption | null, eventDetails: { reason?: string }) => {\n const nextValue = nextOption?.value ?? ''\n const shouldClearSelection = eventDetails.reason === 'item-press' && nextValue === value\n setValue(shouldClearSelection ? '' : nextValue)\n setInputValue('')\n setOpen(false)\n }\n\n const handleOpenChange = (nextOpen: boolean) => {\n if (isDisabled || isLoading) {\n setOpen(false)\n return\n }\n\n if (!nextOpen) {\n setInputValue('')\n }\n\n setOpen(nextOpen)\n }\n\n const handleTriggerContainerClick = (event: MouseEvent<HTMLDivElement>) => {\n if (isDisabled || isLoading) return\n\n const target = event.target as HTMLElement\n if (target === inputRef.current) return\n if (target.closest('[data-slot=combobox-trigger-button]')) return\n inputRef.current?.focus()\n setOpen(true)\n }\n\n return (\n <div className='w-full' ref={ref}>\n {label && (\n <Label className={cn('mb-2 block text-text-primary', labelClassName, isDisabled && 'text-text-secondary')} data-testid='spectral-combobox-label' htmlFor={comboboxId}>\n {label}\n </Label>\n )}\n\n <ComboboxPrimitive.Root\n autoHighlight\n disabled={isDisabled || isLoading}\n filter={null}\n inputValue={inputValue}\n itemToStringLabel={(option: ComboboxOption) => option.label}\n itemToStringValue={(option: ComboboxOption) => option.value}\n openOnInputClick\n onInputValueChange={(nextInputValue, eventDetails) => {\n if (eventDetails.reason !== 'input-change' && eventDetails.reason !== 'input-clear') return\n setInputValue(nextInputValue)\n }}\n name={name}\n onOpenChange={handleOpenChange}\n onValueChange={handleValueChange}\n open={open}\n required={required}\n value={selectedOption}\n >\n <ComboboxPrimitive.InputGroup\n render={\n <InputGroup\n data-slot='combobox-content'\n data-state={state}\n data-testid='spectral-combobox-trigger'\n className={cn(getTriggerClasses(open, state), 'ring-0! outline-none focus-within:outline-none has-[[data-slot=input-group-control]:focus-visible]:outline-0', isDisabled && 'pointer-events-none cursor-not-allowed', className)}\n onClick={handleTriggerContainerClick}\n style={getFormFieldCSSProperties() as CSSProperties}\n />\n }\n >\n <ComboboxPrimitive.Input\n aria-controls={listboxId}\n aria-expanded={open}\n aria-label={ariaLabel ?? label}\n autoComplete='off'\n className={cn(\n 'min-w-0 text-base flex-1 truncate overflow-hidden border-0 bg-transparent whitespace-nowrap text-input-text outline-hidden outline-0 placeholder:opacity-100 focus-visible:ring-0 focus-visible:outline-none',\n showsSelectedValueAsPlaceholder ? 'placeholder:text-input-text!' : 'placeholder:text-input-text-placeholder!',\n )}\n data-slot='input-group-control'\n id={comboboxId}\n onFocus={() => {\n if (suppressNextInputFocusOpenRef.current) {\n suppressNextInputFocusOpenRef.current = false\n return\n }\n\n if (!isDisabled && !isLoading) {\n setOpen(true)\n }\n }}\n ref={inputRef}\n placeholder={selectedOption?.label ?? placeholder}\n {...ariaProps}\n />\n <InputGroupAddon align='inline-end' className='cursor-pointer'>\n <ComboboxPrimitive.Trigger\n aria-label='Toggle options'\n className='cursor-pointer'\n data-slot='combobox-trigger-button'\n onPointerDownCapture={() => {\n const inputElement = inputRef.current\n if (!inputElement) {\n clearFocusSuppression()\n return\n }\n\n const activeElement = inputElement.ownerDocument.activeElement\n suppressNextInputFocusOpenRef.current = activeElement !== inputElement\n }}\n onPointerUpCapture={clearFocusSuppression}\n onPointerCancelCapture={clearFocusSuppression}\n onTouchEndCapture={clearFocusSuppression}\n >\n {isLoading ? <LoaderIcon className='size-5 motion-safe:animate-spin' /> : <ChevronDownIcon className={cn('size-5 shrink-0 transition-transform duration-200', open && 'rotate-180')} />}\n </ComboboxPrimitive.Trigger>\n </InputGroupAddon>\n </ComboboxPrimitive.InputGroup>\n\n <ComboboxPrimitive.Portal>\n <ComboboxPrimitive.Positioner align='start' className='isolate z-50' sideOffset={4}>\n <ComboboxPrimitive.Popup\n className={cn(\n 'p-1 z-50 motion-safe:data-closed:animate-out motion-safe:data-open:animate-in',\n getDropdownSurfaceClasses(),\n 'motion-safe:data-closed:fade-out-0 motion-safe:data-closed:zoom-out-95 motion-safe:data-open:fade-in-0 motion-safe:data-open:zoom-in-95',\n 'max-h-[min(var(--available-height),18rem)] motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=top]:slide-in-from-bottom-2',\n // Keep a single scroll container (the list) to avoid dual scrollbar styles.\n 'min-w-32 origin-(--transform-origin) overflow-hidden',\n )}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-combobox-content'\n style={dropdownWidthStyle}\n >\n {isLoading ? (\n <LoadingState message={loadingMessage} />\n ) : filteredOptions.length === 0 ? (\n <EmptyState message={emptyMessage} />\n ) : (\n <ComboboxPrimitive.List className='max-h-[min(var(--available-height),18rem)] overflow-y-auto' id={listboxId}>\n {filteredOptions.map((option) => (\n <ComboboxPrimitive.Item\n className={cn(getOptionClasses(!!option.disabled, false, value === option.value), 'group/command-item relative flex w-full items-center data-highlighted:bg-input-bg--hover')}\n data-testid='spectral-combobox-item'\n disabled={option.disabled}\n key={option.value}\n value={option}\n >\n <span className='min-w-0 flex-1 truncate whitespace-nowrap'>{option.label}</span>\n <ComboboxPrimitive.ItemIndicator\n render={\n <span className='right-2 h-4 w-4 absolute flex items-center justify-center'>\n <CheckmarkIcon size={16} />\n </span>\n }\n />\n </ComboboxPrimitive.Item>\n ))}\n </ComboboxPrimitive.List>\n )}\n </ComboboxPrimitive.Popup>\n </ComboboxPrimitive.Positioner>\n </ComboboxPrimitive.Portal>\n </ComboboxPrimitive.Root>\n\n <ErrorMessage\n dataTestId='spectral-combobox-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-combobox-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nCombobox.displayName = 'Combobox'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Combobox.js","names":["ComboboxPrimitive"],"sources":["../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { Combobox as ComboboxPrimitive } from '@base-ui/react'\nimport { CheckmarkIcon, ChevronDownIcon, LoaderIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { InputGroup, InputGroupAddon } from '@primitives/input-group'\nimport {\n EmptyState,\n ErrorMessage,\n getAriaProps,\n getDropdownWidthStyles,\n getDropdownSurfaceClasses,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getOptionClasses,\n getTriggerClasses,\n LoadingState,\n WarningMessage,\n useFormFieldId,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useMemo, useRef, useState, type CSSProperties, type MouseEvent, type Ref } from 'react'\n\nexport type ComboboxOption = BaseOption\n\nexport interface ComboboxProps extends Omit<BaseFormFieldProps, 'onChange' | 'state'> {\n className?: string\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n labelClassName?: string\n loadingMessage?: string\n onChange?: (value: string) => void\n onValueChange?: (value: string) => void\n options: ComboboxOption[]\n placeholder?: string\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nexport const Combobox = ({\n className,\n disabled,\n defaultValue = '',\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found.',\n errorMessage,\n id,\n label,\n labelClassName,\n loadingMessage = 'Loading…',\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onValueChange,\n options,\n placeholder = 'Search…',\n ref,\n required,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n}: ComboboxProps & { ref?: Ref<HTMLDivElement> }) => {\n if (process.env.NODE_ENV !== 'production' && !label && !ariaLabel) {\n console.warn('Combobox: provide either `label` or `aria-label` for an accessible name.')\n }\n\n const comboboxId = useFormFieldId(id, name)\n const listboxId = `${comboboxId}-listbox`\n const errorMessageId = getErrorMessageId(comboboxId)\n const warningMessageId = `${comboboxId}-warning`\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const isDisabled = Boolean(disabled)\n const isLoading = state === 'loading'\n const isInvalid = state === 'error'\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n\n const [open, setOpen] = useState(false)\n const [inputValue, setInputValue] = useState('')\n const inputRef = useRef<HTMLInputElement>(null)\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue,\n onChange: (nextValue) => {\n if (onChange) {\n onChange(nextValue)\n } else {\n onValueChange?.(nextValue)\n }\n },\n })\n\n const selectedOption = useMemo(() => options.find((o) => o.value === value) ?? null, [options, value])\n const filteredOptions = useMemo(() => {\n const query = inputValue.trim().toLowerCase()\n if (!query) return options\n return options.filter((option) => option.label.toLowerCase().includes(query))\n }, [inputValue, options])\n const showsSelectedValueAsPlaceholder = Boolean(selectedOption && inputValue.length === 0)\n const { dropdownWidthMode, dropdownWidthStyle } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: 'var(--anchor-width)',\n })\n\n const handleValueChange = (nextOption: ComboboxOption | null, eventDetails: { reason?: string }) => {\n const nextValue = nextOption?.value ?? ''\n const shouldClearSelection = eventDetails.reason === 'item-press' && nextValue === value\n setValue(shouldClearSelection ? '' : nextValue)\n setInputValue('')\n setOpen(false)\n }\n\n const handleOpenChange = (nextOpen: boolean) => {\n if (isDisabled || isLoading) {\n setOpen(false)\n return\n }\n\n if (!nextOpen) {\n setInputValue('')\n }\n\n setOpen(nextOpen)\n }\n\n const handleTriggerContainerClick = (event: MouseEvent<HTMLDivElement>) => {\n if (isDisabled || isLoading) return\n\n const target = event.target as HTMLElement\n if (target === inputRef.current) return\n if (target.closest('[data-slot=combobox-trigger-button]')) return\n inputRef.current?.focus()\n setOpen(true)\n }\n\n return (\n <div className='w-full' ref={ref}>\n {label && (\n <Label className={cn('mb-2 block text-text-primary', labelClassName, isDisabled && 'text-text-secondary')} data-testid='spectral-combobox-label' htmlFor={comboboxId}>\n {label}\n </Label>\n )}\n\n <ComboboxPrimitive.Root\n autoHighlight\n disabled={isDisabled || isLoading}\n filter={null}\n inputValue={inputValue}\n itemToStringLabel={(option: ComboboxOption) => option.label}\n itemToStringValue={(option: ComboboxOption) => option.value}\n openOnInputClick\n onInputValueChange={(nextInputValue, eventDetails) => {\n if (eventDetails.reason !== 'input-change' && eventDetails.reason !== 'input-clear') return\n setInputValue(nextInputValue)\n }}\n name={name}\n onOpenChange={handleOpenChange}\n onValueChange={handleValueChange}\n open={open}\n required={required}\n value={selectedOption}\n >\n <ComboboxPrimitive.InputGroup\n render={\n <InputGroup\n data-slot='combobox-content'\n data-state={state}\n data-testid='spectral-combobox-trigger'\n className={cn(getTriggerClasses(open, state), 'ring-0! outline-none focus-within:outline-none has-[[data-slot=input-group-control]:focus-visible]:outline-0', isDisabled && 'pointer-events-none cursor-not-allowed', className)}\n onClick={handleTriggerContainerClick}\n style={getFormFieldCSSProperties() as CSSProperties}\n />\n }\n >\n <ComboboxPrimitive.Input\n aria-controls={listboxId}\n aria-expanded={open}\n aria-label={ariaLabel ?? label}\n autoComplete='off'\n className={cn(\n 'min-w-0 text-base flex-1 truncate overflow-hidden border-0 bg-transparent whitespace-nowrap text-input-text outline-hidden outline-0 placeholder:opacity-100 focus-visible:ring-0 focus-visible:outline-none',\n showsSelectedValueAsPlaceholder ? 'placeholder:text-input-text!' : 'placeholder:text-input-text-placeholder!',\n )}\n data-slot='input-group-control'\n id={comboboxId}\n ref={inputRef}\n placeholder={selectedOption?.label ?? placeholder}\n {...ariaProps}\n />\n <InputGroupAddon align='inline-end' className='cursor-pointer'>\n <ComboboxPrimitive.Trigger\n aria-label='Toggle options'\n className='cursor-pointer'\n data-slot='combobox-trigger-button'\n >\n {isLoading ? <LoaderIcon className='size-5 motion-safe:animate-spin' /> : <ChevronDownIcon className={cn('size-5 shrink-0 transition-transform duration-200', open && 'rotate-180')} />}\n </ComboboxPrimitive.Trigger>\n </InputGroupAddon>\n </ComboboxPrimitive.InputGroup>\n\n <ComboboxPrimitive.Portal>\n <ComboboxPrimitive.Positioner align='start' className='isolate z-50' sideOffset={4}>\n <ComboboxPrimitive.Popup\n className={cn(\n 'p-1 z-50 motion-safe:data-closed:animate-out motion-safe:data-open:animate-in',\n getDropdownSurfaceClasses(),\n 'motion-safe:data-closed:fade-out-0 motion-safe:data-closed:zoom-out-95 motion-safe:data-open:fade-in-0 motion-safe:data-open:zoom-in-95',\n 'max-h-[min(var(--available-height),18rem)] motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=top]:slide-in-from-bottom-2',\n // Keep a single scroll container (the list) to avoid dual scrollbar styles.\n 'min-w-32 origin-(--transform-origin) overflow-hidden',\n )}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-combobox-content'\n style={dropdownWidthStyle}\n >\n {isLoading ? (\n <LoadingState message={loadingMessage} />\n ) : filteredOptions.length === 0 ? (\n <EmptyState message={emptyMessage} />\n ) : (\n <ComboboxPrimitive.List className='max-h-[min(var(--available-height),18rem)] overflow-y-auto' id={listboxId}>\n {filteredOptions.map((option) => (\n <ComboboxPrimitive.Item\n className={cn(getOptionClasses(!!option.disabled, false, value === option.value), 'group/command-item relative flex w-full items-center data-highlighted:bg-input-bg--hover')}\n data-testid='spectral-combobox-item'\n disabled={option.disabled}\n key={option.value}\n value={option}\n >\n <span className='min-w-0 flex-1 truncate whitespace-nowrap'>{option.label}</span>\n <ComboboxPrimitive.ItemIndicator\n render={\n <span className='right-2 h-4 w-4 absolute flex items-center justify-center'>\n <CheckmarkIcon size={16} />\n </span>\n }\n />\n </ComboboxPrimitive.Item>\n ))}\n </ComboboxPrimitive.List>\n )}\n </ComboboxPrimitive.Popup>\n </ComboboxPrimitive.Positioner>\n </ComboboxPrimitive.Portal>\n </ComboboxPrimitive.Root>\n\n <ErrorMessage\n dataTestId='spectral-combobox-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-combobox-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nCombobox.displayName = 'Combobox'\n"],"mappings":"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"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { CSSProperties, ReactNode, Ref } from "react";
|
|
2
|
+
import { CSSProperties, ReactNode, Ref, RefObject } from "react";
|
|
3
3
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.d.ts
|
|
6
6
|
type DisclosureAlign = 'start' | 'center' | 'end';
|
|
7
7
|
type DisclosureSide = 'top' | 'right' | 'bottom' | 'left';
|
|
8
|
+
/** Pixel offset of the draggable disclosure from where it is mounted (default `{ x: 0, y: 0 }`). */
|
|
9
|
+
interface DirectionalColorWheelDisclosurePosition {
|
|
10
|
+
/** Horizontal offset in px (positive moves right). */
|
|
11
|
+
x: number;
|
|
12
|
+
/** Vertical offset in px (positive moves down). */
|
|
13
|
+
y: number;
|
|
14
|
+
}
|
|
8
15
|
interface DirectionalColorWheelDisclosureProps {
|
|
9
16
|
/** Accessible name for the collapsed trigger button (`aria-expanded` conveys the open state). */
|
|
10
17
|
accessibleName?: string;
|
|
@@ -22,9 +29,23 @@ interface DirectionalColorWheelDisclosureProps {
|
|
|
22
29
|
/** Min distance (px) from the viewport edge before the panel flips/shifts. */
|
|
23
30
|
collisionPadding?: number;
|
|
24
31
|
dataTestId?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Element the drag is clamped to so the glyph can't be parked off the feed. Pass a ref to the
|
|
34
|
+
* container the disclosure is mounted over (e.g. the plot surface). `undefined`/`null` = no clamp
|
|
35
|
+
* (free drag). Only used when `draggable`.
|
|
36
|
+
*/
|
|
37
|
+
dragBoundsRef?: RefObject<HTMLElement | null> | null;
|
|
25
38
|
/** Uncontrolled initial expanded state. */
|
|
26
39
|
defaultExpanded?: boolean;
|
|
40
|
+
/** Uncontrolled initial position offset (px) from the mount point. Defaults to the corner it is mounted at. */
|
|
41
|
+
defaultPosition?: DirectionalColorWheelDisclosurePosition;
|
|
27
42
|
disabled?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* When `true`, the operator can drag the collapsed glyph to reposition the whole widget (the
|
|
45
|
+
* expanded panel reflows with it in real time). A tap still toggles; only a drag past a small
|
|
46
|
+
* slop repositions. Defaults to `false` (fixed at its mount point).
|
|
47
|
+
*/
|
|
48
|
+
draggable?: boolean;
|
|
28
49
|
/**
|
|
29
50
|
* When `false`, the panel stays open during outside interaction and Esc — it toggles only via the
|
|
30
51
|
* trigger (or a controlled `expanded`). Use this for a feed-mounted wheel so clicking/scrubbing the
|
|
@@ -38,6 +59,13 @@ interface DirectionalColorWheelDisclosureProps {
|
|
|
38
59
|
expanded?: boolean | null;
|
|
39
60
|
/** Called whenever the disclosure expands or collapses (click, Esc, or outside dismiss). */
|
|
40
61
|
onExpandedChange?: (expanded: boolean) => void;
|
|
62
|
+
/** Called as the glyph is dragged with the next position offset (px). Pair with `position` to persist it. */
|
|
63
|
+
onPositionChange?: (position: DirectionalColorWheelDisclosurePosition) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Controlled position offset (px) from the mount point. `undefined`/`null` leaves it uncontrolled,
|
|
66
|
+
* so Horizon can gate the feature with a single nullable value (PAT-028). Requires `draggable` to move.
|
|
67
|
+
*/
|
|
68
|
+
position?: DirectionalColorWheelDisclosurePosition | null;
|
|
41
69
|
ref?: Ref<HTMLButtonElement>;
|
|
42
70
|
/** Which side of the trigger the panel grows toward. Defaults to `top` (icon at a bottom corner). */
|
|
43
71
|
side?: DisclosureSide;
|
|
@@ -63,10 +91,15 @@ declare function DirectionalColorWheelDisclosure({
|
|
|
63
91
|
collisionPadding,
|
|
64
92
|
dataTestId,
|
|
65
93
|
defaultExpanded,
|
|
94
|
+
defaultPosition,
|
|
66
95
|
disabled,
|
|
67
96
|
dismissOnInteractOutside,
|
|
97
|
+
draggable,
|
|
98
|
+
dragBoundsRef,
|
|
68
99
|
expanded,
|
|
69
100
|
onExpandedChange,
|
|
101
|
+
onPositionChange,
|
|
102
|
+
position,
|
|
70
103
|
ref,
|
|
71
104
|
side,
|
|
72
105
|
sideOffset,
|
|
@@ -78,5 +111,5 @@ declare namespace DirectionalColorWheelDisclosure {
|
|
|
78
111
|
var displayName: string;
|
|
79
112
|
}
|
|
80
113
|
//#endregion
|
|
81
|
-
export { DirectionalColorWheelDisclosure, DirectionalColorWheelDisclosureProps };
|
|
114
|
+
export { DirectionalColorWheelDisclosure, DirectionalColorWheelDisclosurePosition, DirectionalColorWheelDisclosureProps };
|
|
82
115
|
//# sourceMappingURL=DirectionalColorWheelDisclosure.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DirectionalColorWheelDisclosure.d.ts","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.tsx"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"DirectionalColorWheelDisclosure.d.ts","names":[],"sources":["../../src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.tsx"],"mappings":";;;;;KAOK,eAAA;AAAA,KACA,cAAA;;UAGY,uCAAA;EAJG;EAMlB,CAAA;EANkB;EAQlB,CAAA;AAAA;AAAA,UAOe,oCAAA;;EAEf,cAAA;EAhBiB;EAkBjB,KAAA,GAAQ,eAAA;EAf8C;EAiBtD,QAAA,EAAU,SAAA;EAfV;EAiBA,SAAA;EARe;;;;EAaf,aAAA,GAAgB,SAAA;EAAA;EAEhB,gBAAA;EACA,UAAA;EAUkB;;;;;EAJlB,aAAA,GAAgB,SAAA,CAAU,WAAA;EA0ClB;EAxCR,eAAA;EAwCqB;EAtCrB,eAAA,GAAkB,uCAAA;EAClB,QAAA;EAvBQ;;;;;EA6BR,SAAA;EAlBA;;;;;EAwBA,wBAAA;EAbA;;;;EAkBA,QAAA;EAAA;EAEA,gBAAA,IAAoB,QAAA;EAAA;EAEpB,gBAAA,IAAoB,QAAA,EAAU,uCAAA;EAAA;;;;EAK9B,QAAA,GAAW,uCAAA;EACX,GAAA,GAAM,GAAA,CAAI,iBAAA;EAAA;EAEV,IAAA,GAAO,cAAA;EAAA;EAEP,UAAA;EAMA;;;;;EAAA,KAAA,GAAQ,aAAA;;EAER,gBAAA;;EAEA,KAAA;AAAA;AAAA;EAoBA,cAAA;EACA,KAAA;EACA,QAAA;EACA,SAAA;EACA,aAAA;EACA,gBAAA;EACA,UAAA;EACA,eAAA;EACA,eAAA;EACA,QAAA;EACA,wBAAA;EACA,SAAA;EACA,aAAA;EACA,QAAA;EACA,gBAAA;EACA,gBAAA;EACA,QAAA;EACA,GAAA;EACA,IAAA;EACA,UAAA;EACA,KAAA;EACA,gBAAA;EACA;AAAA,GACC,oCAAA,GAAoC,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { cn } from "../utils/twUtils.js";
|
|
3
|
+
import { useUncontrolledState } from "../hooks/useUncontrolledState.js";
|
|
3
4
|
import { Card } from "../DataCard/Card.js";
|
|
4
5
|
import { Popover, PopoverContent, PopoverTrigger } from "../Popover.js";
|
|
5
6
|
import { DirectionalColorWheelGlyph } from "./DirectionalColorWheelGlyph.js";
|
|
6
|
-
import { useRef } from "react";
|
|
7
|
+
import { useCallback, useRef, useState } from "react";
|
|
7
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
9
|
|
|
9
10
|
//#region src/components/DirectionalColorWheel/DirectionalColorWheelDisclosure.tsx
|
|
11
|
+
const DRAG_SLOP_PX = 4;
|
|
12
|
+
const ORIGIN = {
|
|
13
|
+
x: 0,
|
|
14
|
+
y: 0
|
|
15
|
+
};
|
|
10
16
|
/**
|
|
11
17
|
* Collapse/expand affordance for the {@link DirectionalColorWheel}, built on the Spectral
|
|
12
18
|
* {@link Popover} primitive so it inherits corner anchoring, click-outside/Esc dismissal, focus
|
|
@@ -16,10 +22,83 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
16
22
|
* panel, growing from the icon with a `transform-origin`-anchored zoom+fade that is automatically
|
|
17
23
|
* skipped under `prefers-reduced-motion`. `className` extends the Card panel's classes and `style`
|
|
18
24
|
* applies inline overrides to it (background, border, shadow, …).
|
|
25
|
+
*
|
|
26
|
+
* Pass `draggable` to let the operator drag the glyph to reposition the whole widget on a feed; the
|
|
27
|
+
* expanded panel reflows with it in real time, and `dragBoundsRef` clamps it inside its container so
|
|
28
|
+
* it can't be parked off the feed. Position is controlled+uncontrolled via
|
|
29
|
+
* `position` / `defaultPosition` / `onPositionChange` (a nullable px offset from the mount point,
|
|
30
|
+
* PAT-028) so a consumer can persist where the operator parked it.
|
|
19
31
|
*/
|
|
20
|
-
const DirectionalColorWheelDisclosure = ({ accessibleName = "Directional color legend", align = "start", children, className, collapsedIcon, collisionPadding = 8, dataTestId = "spectral-directional-color-wheel-disclosure", defaultExpanded, disabled = false, dismissOnInteractOutside = true, expanded, onExpandedChange, ref, side = "top", sideOffset = 8, style, triggerClassName, width = "fit-content" }) => {
|
|
32
|
+
const DirectionalColorWheelDisclosure = ({ accessibleName = "Directional color legend", align = "start", children, className, collapsedIcon, collisionPadding = 8, dataTestId = "spectral-directional-color-wheel-disclosure", defaultExpanded, defaultPosition, disabled = false, dismissOnInteractOutside = true, draggable = false, dragBoundsRef, expanded, onExpandedChange, onPositionChange, position, ref, side = "top", sideOffset = 8, style, triggerClassName, width = "fit-content" }) => {
|
|
21
33
|
const panelRef = useRef(null);
|
|
22
34
|
const panelWidth = typeof width === "number" ? `${width}px` : width;
|
|
35
|
+
const [positionValue, setPositionValue] = useUncontrolledState({
|
|
36
|
+
defaultValue: defaultPosition ?? ORIGIN,
|
|
37
|
+
onChange: onPositionChange,
|
|
38
|
+
value: position ?? void 0
|
|
39
|
+
});
|
|
40
|
+
const [dragging, setDragging] = useState(false);
|
|
41
|
+
const dragRef = useRef(null);
|
|
42
|
+
const suppressClickRef = useRef(false);
|
|
43
|
+
const handleTriggerPointerDown = useCallback((event) => {
|
|
44
|
+
suppressClickRef.current = false;
|
|
45
|
+
if (!draggable || disabled) return;
|
|
46
|
+
try {
|
|
47
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
48
|
+
} catch {}
|
|
49
|
+
const triggerRect = event.currentTarget.getBoundingClientRect();
|
|
50
|
+
dragRef.current = {
|
|
51
|
+
baseX: positionValue.x,
|
|
52
|
+
baseY: positionValue.y,
|
|
53
|
+
bounds: dragBoundsRef?.current?.getBoundingClientRect() ?? null,
|
|
54
|
+
height: triggerRect.height,
|
|
55
|
+
homeLeft: triggerRect.left - positionValue.x,
|
|
56
|
+
homeTop: triggerRect.top - positionValue.y,
|
|
57
|
+
moved: false,
|
|
58
|
+
pointerId: event.pointerId,
|
|
59
|
+
startX: event.clientX,
|
|
60
|
+
startY: event.clientY,
|
|
61
|
+
width: triggerRect.width
|
|
62
|
+
};
|
|
63
|
+
}, [
|
|
64
|
+
disabled,
|
|
65
|
+
draggable,
|
|
66
|
+
dragBoundsRef,
|
|
67
|
+
positionValue.x,
|
|
68
|
+
positionValue.y
|
|
69
|
+
]);
|
|
70
|
+
const handleTriggerPointerMove = useCallback((event) => {
|
|
71
|
+
const drag = dragRef.current;
|
|
72
|
+
if (drag === null || drag.pointerId !== event.pointerId) return;
|
|
73
|
+
const deltaX = event.clientX - drag.startX;
|
|
74
|
+
const deltaY = event.clientY - drag.startY;
|
|
75
|
+
if (!drag.moved) {
|
|
76
|
+
if (Math.hypot(deltaX, deltaY) <= DRAG_SLOP_PX) return;
|
|
77
|
+
drag.moved = true;
|
|
78
|
+
suppressClickRef.current = true;
|
|
79
|
+
setDragging(true);
|
|
80
|
+
}
|
|
81
|
+
let nextX = drag.baseX + deltaX;
|
|
82
|
+
let nextY = drag.baseY + deltaY;
|
|
83
|
+
if (drag.bounds !== null) {
|
|
84
|
+
nextX = Math.min(Math.max(nextX, drag.bounds.left - drag.homeLeft), drag.bounds.right - drag.homeLeft - drag.width);
|
|
85
|
+
nextY = Math.min(Math.max(nextY, drag.bounds.top - drag.homeTop), drag.bounds.bottom - drag.homeTop - drag.height);
|
|
86
|
+
}
|
|
87
|
+
setPositionValue({
|
|
88
|
+
x: nextX,
|
|
89
|
+
y: nextY
|
|
90
|
+
});
|
|
91
|
+
}, [setPositionValue]);
|
|
92
|
+
const endDrag = useCallback(() => {
|
|
93
|
+
dragRef.current = null;
|
|
94
|
+
setDragging(false);
|
|
95
|
+
}, []);
|
|
96
|
+
const handleTriggerClick = useCallback((event) => {
|
|
97
|
+
if (suppressClickRef.current) {
|
|
98
|
+
suppressClickRef.current = false;
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
}
|
|
101
|
+
}, []);
|
|
23
102
|
return /* @__PURE__ */ jsxs(Popover, {
|
|
24
103
|
defaultOpen: defaultExpanded,
|
|
25
104
|
onOpenChange: onExpandedChange,
|
|
@@ -28,11 +107,20 @@ const DirectionalColorWheelDisclosure = ({ accessibleName = "Directional color l
|
|
|
28
107
|
asChild: true,
|
|
29
108
|
children: /* @__PURE__ */ jsx("button", {
|
|
30
109
|
"aria-label": accessibleName,
|
|
31
|
-
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),
|
|
110
|
+
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", draggable && "touch-none", draggable && (dragging ? "cursor-grabbing" : "cursor-grab"), triggerClassName),
|
|
32
111
|
"data-slot": "directional-color-wheel-disclosure-trigger",
|
|
33
112
|
"data-testid": `${dataTestId}-trigger`,
|
|
34
113
|
disabled,
|
|
114
|
+
onClick: handleTriggerClick,
|
|
115
|
+
onLostPointerCapture: endDrag,
|
|
116
|
+
onPointerDown: handleTriggerPointerDown,
|
|
117
|
+
onPointerMove: handleTriggerPointerMove,
|
|
118
|
+
onPointerUp: endDrag,
|
|
35
119
|
ref,
|
|
120
|
+
style: draggable ? {
|
|
121
|
+
transform: `translate(${positionValue.x}px, ${positionValue.y}px)`,
|
|
122
|
+
willChange: dragging ? "transform" : void 0
|
|
123
|
+
} : void 0,
|
|
36
124
|
type: "button",
|
|
37
125
|
children: collapsedIcon ?? /* @__PURE__ */ jsx(DirectionalColorWheelGlyph, {})
|
|
38
126
|
})
|
|
@@ -50,6 +138,7 @@ const DirectionalColorWheelDisclosure = ({ accessibleName = "Directional color l
|
|
|
50
138
|
},
|
|
51
139
|
side,
|
|
52
140
|
sideOffset,
|
|
141
|
+
updatePositionStrategy: dragging ? "always" : "optimized",
|
|
53
142
|
width: "fit-content",
|
|
54
143
|
children: /* @__PURE__ */ jsx(Card, {
|
|
55
144
|
className: cn("outline-none", className),
|
|
@@ -1 +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 CSSProperties, 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 * When `false`, the panel stays open during outside interaction and Esc — it toggles only via the\n * trigger (or a controlled `expanded`). Use this for a feed-mounted wheel so clicking/scrubbing the\n * feed underneath doesn't dismiss the legend. Defaults to `true` (standard popover dismissal).\n */\n dismissOnInteractOutside?: 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 /**\n * Inline styles merged onto the expanded panel (the {@link Card}). Use this for visual overrides\n * — background, border, shadow — that the `card-effects` base class doesn't expose to `className`\n * utilities. The default panel width is applied first, so a `width` set here wins over the `width` prop.\n */\n style?: CSSProperties\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's classes and `style`\n * applies inline overrides to it (background, border, shadow, …).\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 dismissOnInteractOutside = true,\n expanded,\n onExpandedChange,\n ref,\n side = 'top',\n sideOffset = 8,\n style,\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 // When dismissal is off, swallow Radix's outside-pointer/Esc dismiss so the panel stays open\n // while the user interacts with whatever sits under it (e.g. the feed); the trigger still toggles.\n onEscapeKeyDown={dismissOnInteractOutside ? undefined : (event) => event.preventDefault()}\n onInteractOutside={dismissOnInteractOutside ? undefined : (event) => event.preventDefault()}\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 // The default width is applied first so a caller-supplied `style.width` (or any other\n // visual override the `card-effects` base class locks down) takes precedence.\n style={{ width: panelWidth, ...style }}\n tabIndex={-1}\n >\n {children}\n </Card>\n </PopoverContent>\n </Popover>\n )\n}\n\nDirectionalColorWheelDisclosure.displayName = 'DirectionalColorWheelDisclosure'\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqEA,MAAa,mCAAmC,EAC9C,iBAAiB,4BACjB,QAAQ,SACR,UACA,WACA,eACA,mBAAmB,GACnB,aAAa,+CACb,iBACA,WAAW,OACX,2BAA2B,MAC3B,UACA,kBACA,KACA,OAAO,OACP,aAAa,GACb,OACA,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;GAG3B,iBAAiB,2BAA2B,UAAa,UAAU,MAAM,gBAAgB;GACzF,mBAAmB,2BAA2B,UAAa,UAAU,MAAM,gBAAgB;GAC3F,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;IAGL,OAAO;KAAE,OAAO;KAAY,GAAG;KAAO;IACtC,UAAU;IAET;IACI;GACQ,EACT;;;AAId,gCAAgC,cAAc"}
|
|
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 { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useRef, useState, type CSSProperties, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode, type Ref, type RefObject } from 'react'\nimport { DirectionalColorWheelGlyph } from './DirectionalColorWheelGlyph'\n\ntype DisclosureAlign = 'start' | 'center' | 'end'\ntype DisclosureSide = 'top' | 'right' | 'bottom' | 'left'\n\n/** Pixel offset of the draggable disclosure from where it is mounted (default `{ x: 0, y: 0 }`). */\nexport interface DirectionalColorWheelDisclosurePosition {\n /** Horizontal offset in px (positive moves right). */\n x: number\n /** Vertical offset in px (positive moves down). */\n y: number\n}\n\n// Drag distance (px) below which a press on the glyph is a tap (toggle), not a reposition drag.\nconst DRAG_SLOP_PX = 4\nconst ORIGIN: DirectionalColorWheelDisclosurePosition = { x: 0, y: 0 }\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 /**\n * Element the drag is clamped to so the glyph can't be parked off the feed. Pass a ref to the\n * container the disclosure is mounted over (e.g. the plot surface). `undefined`/`null` = no clamp\n * (free drag). Only used when `draggable`.\n */\n dragBoundsRef?: RefObject<HTMLElement | null> | null\n /** Uncontrolled initial expanded state. */\n defaultExpanded?: boolean\n /** Uncontrolled initial position offset (px) from the mount point. Defaults to the corner it is mounted at. */\n defaultPosition?: DirectionalColorWheelDisclosurePosition\n disabled?: boolean\n /**\n * When `true`, the operator can drag the collapsed glyph to reposition the whole widget (the\n * expanded panel reflows with it in real time). A tap still toggles; only a drag past a small\n * slop repositions. Defaults to `false` (fixed at its mount point).\n */\n draggable?: boolean\n /**\n * When `false`, the panel stays open during outside interaction and Esc — it toggles only via the\n * trigger (or a controlled `expanded`). Use this for a feed-mounted wheel so clicking/scrubbing the\n * feed underneath doesn't dismiss the legend. Defaults to `true` (standard popover dismissal).\n */\n dismissOnInteractOutside?: 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 /** Called as the glyph is dragged with the next position offset (px). Pair with `position` to persist it. */\n onPositionChange?: (position: DirectionalColorWheelDisclosurePosition) => void\n /**\n * Controlled position offset (px) from the mount point. `undefined`/`null` leaves it uncontrolled,\n * so Horizon can gate the feature with a single nullable value (PAT-028). Requires `draggable` to move.\n */\n position?: DirectionalColorWheelDisclosurePosition | null\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 /**\n * Inline styles merged onto the expanded panel (the {@link Card}). Use this for visual overrides\n * — background, border, shadow — that the `card-effects` base class doesn't expose to `className`\n * utilities. The default panel width is applied first, so a `width` set here wins over the `width` prop.\n */\n style?: CSSProperties\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's classes and `style`\n * applies inline overrides to it (background, border, shadow, …).\n *\n * Pass `draggable` to let the operator drag the glyph to reposition the whole widget on a feed; the\n * expanded panel reflows with it in real time, and `dragBoundsRef` clamps it inside its container so\n * it can't be parked off the feed. Position is controlled+uncontrolled via\n * `position` / `defaultPosition` / `onPositionChange` (a nullable px offset from the mount point,\n * PAT-028) so a consumer can persist where the operator parked it.\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 defaultPosition,\n disabled = false,\n dismissOnInteractOutside = true,\n draggable = false,\n dragBoundsRef,\n expanded,\n onExpandedChange,\n onPositionChange,\n position,\n ref,\n side = 'top',\n sideOffset = 8,\n style,\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 const [positionValue, setPositionValue] = useUncontrolledState<DirectionalColorWheelDisclosurePosition>({ defaultValue: defaultPosition ?? ORIGIN, onChange: onPositionChange, value: position ?? undefined })\n const [dragging, setDragging] = useState(false)\n // Live reposition drag (a ref so rapid moves don't hinge on re-render timing). `bounds` is the\n // clamp rect (the dragBoundsRef element); `homeLeft`/`homeTop` are the trigger's viewport position\n // at translate 0, so the allowed offset range keeps the glyph fully inside `bounds`.\n const dragRef = useRef<{ baseX: number; baseY: number; bounds: DOMRect | null; height: number; homeLeft: number; homeTop: number; moved: boolean; pointerId: number; startX: number; startY: number; width: number } | null>(null)\n // Set when a drag passes the slop so the trailing click is swallowed (preventDefault → Radix's\n // composed trigger toggle bails) instead of also expanding/collapsing the panel.\n const suppressClickRef = useRef(false)\n\n const handleTriggerPointerDown = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>) => {\n suppressClickRef.current = false\n if (!draggable || 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 triggerRect = event.currentTarget.getBoundingClientRect()\n dragRef.current = {\n baseX: positionValue.x,\n baseY: positionValue.y,\n bounds: dragBoundsRef?.current?.getBoundingClientRect() ?? null,\n height: triggerRect.height,\n // Subtract the active translate to recover the trigger's home (translate-0) position.\n homeLeft: triggerRect.left - positionValue.x,\n homeTop: triggerRect.top - positionValue.y,\n moved: false,\n pointerId: event.pointerId,\n startX: event.clientX,\n startY: event.clientY,\n width: triggerRect.width,\n }\n },\n [disabled, draggable, dragBoundsRef, positionValue.x, positionValue.y],\n )\n\n const handleTriggerPointerMove = useCallback(\n (event: ReactPointerEvent<HTMLButtonElement>) => {\n const drag = dragRef.current\n if (drag === null || drag.pointerId !== event.pointerId) return\n const deltaX = event.clientX - drag.startX\n const deltaY = event.clientY - drag.startY\n if (!drag.moved) {\n if (Math.hypot(deltaX, deltaY) <= DRAG_SLOP_PX) return\n drag.moved = true\n suppressClickRef.current = true\n // Promote the trigger to its own compositor layer and poll the popover position every frame\n // (`updatePositionStrategy='always'`) so the panel reflows with the glyph rather than snapping.\n setDragging(true)\n }\n let nextX = drag.baseX + deltaX\n let nextY = drag.baseY + deltaY\n // Clamp the offset so the glyph stays fully inside the bounds element (can't be lost off the feed).\n if (drag.bounds !== null) {\n nextX = Math.min(Math.max(nextX, drag.bounds.left - drag.homeLeft), drag.bounds.right - drag.homeLeft - drag.width)\n nextY = Math.min(Math.max(nextY, drag.bounds.top - drag.homeTop), drag.bounds.bottom - drag.homeTop - drag.height)\n }\n setPositionValue({ x: nextX, y: nextY })\n },\n [setPositionValue],\n )\n\n const endDrag = useCallback(() => {\n dragRef.current = null\n setDragging(false)\n }, [])\n\n const handleTriggerClick = useCallback((event: ReactMouseEvent<HTMLButtonElement>) => {\n if (suppressClickRef.current) {\n suppressClickRef.current = false\n event.preventDefault()\n }\n }, [])\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 draggable && 'touch-none',\n draggable && (dragging ? 'cursor-grabbing' : 'cursor-grab'),\n triggerClassName,\n )}\n data-slot='directional-color-wheel-disclosure-trigger'\n data-testid={`${dataTestId}-trigger`}\n disabled={disabled}\n onClick={handleTriggerClick}\n onLostPointerCapture={endDrag}\n onPointerDown={handleTriggerPointerDown}\n onPointerMove={handleTriggerPointerMove}\n onPointerUp={endDrag}\n ref={ref}\n // A pure translate (no left/top) keeps the reposition on the compositor; `will-change`\n // promotes it for the duration of the drag.\n style={draggable ? { transform: `translate(${positionValue.x}px, ${positionValue.y}px)`, willChange: dragging ? 'transform' : undefined } : undefined}\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 // When dismissal is off, swallow Radix's outside-pointer/Esc dismiss so the panel stays open\n // while the user interacts with whatever sits under it (e.g. the feed); the trigger still toggles.\n onEscapeKeyDown={dismissOnInteractOutside ? undefined : (event) => event.preventDefault()}\n onInteractOutside={dismissOnInteractOutside ? undefined : (event) => event.preventDefault()}\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 // While dragging, poll the anchor position every animation frame so the panel tracks the\n // glyph in real time; back to the cheaper 'optimized' strategy at rest.\n updatePositionStrategy={dragging ? 'always' : 'optimized'}\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 // The default width is applied first so a caller-supplied `style.width` (or any other\n // visual override the `card-effects` base class locks down) takes precedence.\n style={{ width: panelWidth, ...style }}\n tabIndex={-1}\n >\n {children}\n </Card>\n </PopoverContent>\n </Popover>\n )\n}\n\nDirectionalColorWheelDisclosure.displayName = 'DirectionalColorWheelDisclosure'\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DirectionalColorWheel.d.ts","names":[],"sources":["../src/components/DirectionalColorWheel/DirectionalColorWheel.tsx"],"mappings":";;;;;;UAKiB,0BAAA;;EAEf,cAAA;EAFe;;;;;;;EAUf,OAAA;EAuCoC;EArCpC,SAAA;EAFA;;;;;;;EAUA,UAAA;EAWA;;;;;EALA,eAAA,KAAoB,cAAA;EAcE;EAZtB,UAAA,GAAa,oBAAA;EACb,UAAA;EAeA;EAbA,iBAAA;EAauC;EAXvC,sBAAA;EAaqB;EAXrB,gBAAA;EACA,QAAA;EAWU;EATV,eAAA;EAWc;EATd,kBAAA,IAAsB,UAAA;EAiBtB;EAfA,uBAAA,IAA2B,eAAA;EAiBd;EAfb,cAAA,IAAkB,WAAA,UAAqB,YAAA;;EAEvC,iBAAA,IAAqB,SAAA;EACrB,GAAA,GAAM,GAAA,CAAI,cAAA;
|
|
1
|
+
{"version":3,"file":"DirectionalColorWheel.d.ts","names":[],"sources":["../src/components/DirectionalColorWheel/DirectionalColorWheel.tsx"],"mappings":";;;;;;UAKiB,0BAAA;;EAEf,cAAA;EAFe;;;;;;;EAUf,OAAA;EAuCoC;EArCpC,SAAA;EAFA;;;;;;;EAUA,UAAA;EAWA;;;;;EALA,eAAA,KAAoB,cAAA;EAcE;EAZtB,UAAA,GAAa,oBAAA;EACb,UAAA;EAeA;EAbA,iBAAA;EAauC;EAXvC,sBAAA;EAaqB;EAXrB,gBAAA;EACA,QAAA;EAWU;EATV,eAAA;EAWc;EATd,kBAAA,IAAsB,UAAA;EAiBtB;EAfA,uBAAA,IAA2B,eAAA;EAiBd;EAfb,cAAA,IAAkB,WAAA,UAAqB,YAAA;;EAEvC,iBAAA,IAAqB,SAAA;EACrB,GAAA,GAAM,GAAA,CAAI,cAAA;EAkGV;EAhGA,WAAA,GAAc,sBAAA;EAkGd;EAhGA,IAAA;EAkGA;;;;;EA5FA,SAAA;EAkGA;EAhGA,aAAA;AAAA;AAAA;EAsFA,cAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,eAAA;EACA,UAAA;EACA,UAAA;EACA,iBAAA;EACA,sBAAA;EACA,gBAAA;EACA,QAAA;EACA,eAAA;EACA,kBAAA;EACA,uBAAA;EACA,cAAA;EACA,iBAAA;EACA,GAAA;EACA,WAAA;EACA,IAAA;EACA,SAAA;EACA;AAAA,GACC,0BAAA,GAA0B,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
|