UNPKG

@base-ui/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

78 lines (75 loc) 2.6 kB
import { createSelector, ReactStore } from '@base-ui/utils/store'; import { createEventEmitter } from "../utils/createEventEmitter.js"; import { isClickLikeEvent } from "../utils.js"; const selectors = { open: createSelector(state => state.open), transitionStatus: createSelector(state => state.transitionStatus), domReferenceElement: createSelector(state => state.domReferenceElement), referenceElement: createSelector(state => state.positionReference ?? state.referenceElement), floatingElement: createSelector(state => state.floatingElement), floatingId: createSelector(state => state.floatingId) }; export class FloatingRootStore extends ReactStore { constructor(options) { const { syncOnly, nested, onOpenChange, triggerElements, ...initialState } = options; super({ ...initialState, positionReference: initialState.referenceElement, domReferenceElement: initialState.referenceElement }, { onOpenChange, dataRef: { current: {} }, events: createEventEmitter(), nested, triggerElements }, selectors); this.syncOnly = syncOnly; } /** * Syncs the event used by hover logic to distinguish hover-open from click-like interaction. */ syncOpenEvent = (newOpen, event) => { if (!newOpen || !this.state.open || // Prevent a pending hover-open from overwriting a click-open event, while allowing // click events to upgrade a hover-open. event != null && isClickLikeEvent(event)) { this.context.dataRef.current.openEvent = newOpen ? event : undefined; } }; /** * Runs the root-owned side effects for an open state change. */ dispatchOpenChange = (newOpen, eventDetails) => { this.syncOpenEvent(newOpen, eventDetails.event); const details = { open: newOpen, reason: eventDetails.reason, nativeEvent: eventDetails.event, nested: this.context.nested, triggerElement: eventDetails.trigger }; this.context.events.emit('openchange', details); }; /** * Emits the `openchange` event through the internal event emitter and calls the `onOpenChange` handler with the provided arguments. * * @param newOpen The new open state. * @param eventDetails Details about the event that triggered the open state change. */ setOpen = (newOpen, eventDetails) => { if (this.syncOnly) { this.context.onOpenChange?.(newOpen, eventDetails); return; } this.dispatchOpenChange(newOpen, eventDetails); this.context.onOpenChange?.(newOpen, eventDetails); }; }