UNPKG

@corvu/disclosure

Version:

Unstyled, accessible and customizable UI primitives for SolidJS

315 lines (309 loc) 9.39 kB
// src/Content.tsx import { combineStyle } from "@corvu/utils/dom"; import { createMemo, Show, splitProps } from "solid-js"; import { Dynamic } from "@corvu/utils/dynamic"; import { mergeRefs, some } from "@corvu/utils/reactivity"; import { dataIf } from "@corvu/utils"; // src/context.ts import { createContext, useContext } from "solid-js"; import { createKeyedContext, useKeyedContext } from "@corvu/utils/create/keyedContext"; var DisclosureContext = createContext(); var createDisclosureContext = (contextId) => { if (contextId === void 0) return DisclosureContext; const context = createKeyedContext( `disclosure-${contextId}` ); return context; }; var useDisclosureContext = (contextId) => { if (contextId === void 0) { const context2 = useContext(DisclosureContext); if (!context2) { throw new Error( "[corvu]: Disclosure context not found. Make sure to wrap Disclosure components in <Disclosure.Root>" ); } return context2; } const context = useKeyedContext( `disclosure-${contextId}` ); if (!context) { throw new Error( `[corvu]: Disclosure context with id "${contextId}" not found. Make sure to wrap Disclosure components in <Disclosure.Root contextId="${contextId}">` ); } return context; }; var InternalDisclosureContext = createContext(); var createInternalDisclosureContext = (contextId) => { if (contextId === void 0) return InternalDisclosureContext; const context = createKeyedContext( `disclosure-internal-${contextId}` ); return context; }; var useInternalDisclosureContext = (contextId) => { if (contextId === void 0) { const context2 = useContext(InternalDisclosureContext); if (!context2) { throw new Error( "[corvu]: Disclosure context not found. Make sure to wrap Disclosure components in <Disclosure.Root>" ); } return context2; } const context = useKeyedContext( `disclosure-internal-${contextId}` ); if (!context) { throw new Error( `[corvu]: Disclosure context with id "${contextId}" not found. Make sure to wrap Disclosure components in <Disclosure.Root contextId="${contextId}">` ); } return context; }; // src/Content.tsx var DisclosureContent = (props) => { const [localProps, otherProps] = splitProps(props, [ "forceMount", "contextId", "ref", "style" ]); const context = createMemo( () => useInternalDisclosureContext(localProps.contextId) ); const show = () => some( context().expanded, () => localProps.forceMount, context().contentPresent ); const contentWidth = createMemo(() => { const contentSize = context().contentSize(); return contentSize ? contentSize[0] : void 0; }); const contentHeight = createMemo(() => { const contentSize = context().contentSize(); return contentSize ? contentSize[1] : void 0; }); return <Show when={show() || context().collapseBehavior() === "hide"}> <Dynamic as="div" ref={mergeRefs(context().setContentRef, localProps.ref)} style={combineStyle( { display: !show() ? "none" : void 0, "--corvu-disclosure-content-width": `${contentWidth()}px`, "--corvu-disclosure-content-height": `${contentHeight()}px` }, localProps.style )} id={context().disclosureId()} data-expanded={dataIf(context().expanded())} data-collapsed={dataIf(!context().expanded())} data-corvu-disclosure-content="" {...otherProps} /> </Show>; }; var Content_default = DisclosureContent; // src/Root.tsx import { createEffect, createMemo as createMemo2, createSignal, createUniqueId, mergeProps, on, untrack } from "solid-js"; import createControllableSignal from "@corvu/utils/create/controllableSignal"; import createOnce from "@corvu/utils/create/once"; import createPresence from "solid-presence"; import { isFunction } from "@corvu/utils"; var DisclosureRoot = (props) => { const defaultedProps = mergeProps( { initialExpanded: false, collapseBehavior: "remove", preventInitialContentAnimation: false, disclosureId: createUniqueId() }, props ); const [expanded, setExpanded] = createControllableSignal({ value: () => defaultedProps.expanded, initialValue: defaultedProps.initialExpanded, onChange: defaultedProps.onExpandedChange }); const [contentRef, setContentRef] = createSignal(null); const [contentSize, setContentSize] = createSignal( null ); const { present: contentPresent } = createPresence({ show: expanded, element: contentRef, onStateChange: (state) => { if (state === "present") { defaultedProps.onContentPresentChange?.(true); } else if (state === "hidden") { defaultedProps.onContentPresentChange?.(false); } } }); let initialOpen = defaultedProps.preventInitialContentAnimation && expanded(); createEffect((animationName) => { expanded(); const element = contentRef(); if (!element) return; if (initialOpen) { const origionalAnimationName = element.style.animationName; element.style.animationName = "none"; initialOpen = false; return origionalAnimationName; } if (animationName === void 0) return; element.style.animationName = animationName; }); createEffect( on([contentRef, contentPresent], ([element]) => { if (!element) { setContentSize(null); return; } untrack(() => { const origionalAnimationName = element.style.animationName; element.style.animationName = "none"; const width = element.offsetWidth; const height = element.offsetHeight; const currentSize = contentSize(); if (currentSize === null || width !== currentSize[0] || height !== currentSize[1]) { setContentSize([element.offsetWidth, element.offsetHeight]); } element.style.animationName = origionalAnimationName; }); }) ); const childrenProps = { get expanded() { return expanded(); }, setExpanded, get collapseBehavior() { return defaultedProps.collapseBehavior; }, get preventInitialContentAnimation() { return defaultedProps.preventInitialContentAnimation; }, get disclosureId() { return defaultedProps.disclosureId; }, get contentPresent() { return contentPresent(); }, get contentRef() { return contentRef(); }, get contentSize() { return contentSize(); } }; const memoizedChildren = createOnce(() => defaultedProps.children); const resolveChildren = () => { const children = memoizedChildren()(); if (isFunction(children)) { return children(childrenProps); } return children; }; const memoizedDisclosureRoot = createMemo2(() => { const DisclosureContext2 = createDisclosureContext(defaultedProps.contextId); const InternalDisclosureContext2 = createInternalDisclosureContext( defaultedProps.contextId ); return <DisclosureContext2.Provider value={{ expanded, setExpanded, collapseBehavior: () => defaultedProps.collapseBehavior, preventInitialContentAnimation: () => defaultedProps.preventInitialContentAnimation, disclosureId: () => defaultedProps.disclosureId, contentPresent, contentRef, contentSize }} > <InternalDisclosureContext2.Provider value={{ expanded, setExpanded, collapseBehavior: () => defaultedProps.collapseBehavior, preventInitialContentAnimation: () => defaultedProps.preventInitialContentAnimation, disclosureId: () => defaultedProps.disclosureId, contentPresent, contentRef, setContentRef, contentSize }} > {untrack(() => resolveChildren())} </InternalDisclosureContext2.Provider> </DisclosureContext2.Provider>; }); return memoizedDisclosureRoot; }; var Root_default = DisclosureRoot; // src/Trigger.tsx import { callEventHandler } from "@corvu/utils/dom"; import { createMemo as createMemo3, splitProps as splitProps2 } from "solid-js"; import { DynamicButton } from "@corvu/utils/dynamic"; import { dataIf as dataIf2 } from "@corvu/utils"; var DisclosureTrigger = (props) => { const [localProps, otherProps] = splitProps2(props, [ "contextId", "onClick" ]); const context = createMemo3( () => useInternalDisclosureContext(localProps.contextId) ); const onClick = (e) => { !callEventHandler(localProps.onClick, e) && context().setExpanded((expanded) => !expanded); }; return <DynamicButton onClick={onClick} aria-controls={context().disclosureId()} aria-expanded={context().expanded() ? "true" : "false"} data-collapsed={dataIf2(!context().expanded())} data-expanded={dataIf2(context().expanded())} data-corvu-disclosure-trigger="" {...otherProps} />; }; var Trigger_default = DisclosureTrigger; // src/index.ts var Disclosure = Object.assign(Root_default, { Trigger: Trigger_default, Content: Content_default, useContext: useDisclosureContext }); var index_default = Disclosure; export { Content_default as Content, Root_default as Root, Trigger_default as Trigger, index_default as default, useDisclosureContext as useContext };