@corvu/disclosure
Version:
Unstyled, accessible and customizable UI primitives for SolidJS
315 lines (309 loc) • 9.39 kB
JSX
// 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
};