@corvu/disclosure
Version:
Unstyled, accessible and customizable UI primitives for SolidJS
277 lines (272 loc) • 9.51 kB
JavaScript
import { createComponent, mergeProps as mergeProps$1 } from 'solid-js/web';
import { combineStyle, callEventHandler } from '@corvu/utils/dom';
import { createContext, mergeProps, createUniqueId, createSignal, createEffect, on, untrack, createMemo, useContext, splitProps, Show } from 'solid-js';
import { Dynamic, DynamicButton } from '@corvu/utils/dynamic';
import { some, mergeRefs } from '@corvu/utils/reactivity';
import { dataIf, isFunction } from '@corvu/utils';
import { useKeyedContext, createKeyedContext } from '@corvu/utils/create/keyedContext';
import createControllableSignal from '@corvu/utils/create/controllableSignal';
import createOnce from '@corvu/utils/create/once';
import createPresence from 'solid-presence';
// src/Content.tsx
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 createComponent(Show, {
get when() {
return show() || context().collapseBehavior() === "hide";
},
get children() {
return createComponent(Dynamic, mergeProps$1({
as: "div",
ref(r$) {
var _ref$ = mergeRefs(context().setContentRef, localProps.ref);
typeof _ref$ === "function" && _ref$(r$);
},
get style() {
return combineStyle({
display: !show() ? "none" : void 0,
"--corvu-disclosure-content-width": `${contentWidth()}px`,
"--corvu-disclosure-content-height": `${contentHeight()}px`
}, localProps.style);
},
get id() {
return context().disclosureId();
},
get ["data-expanded"]() {
return dataIf(context().expanded());
},
get ["data-collapsed"]() {
return dataIf(!context().expanded());
},
"data-corvu-disclosure-content": ""
}, otherProps));
}
});
};
var Content_default = DisclosureContent;
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 = createMemo(() => {
const DisclosureContext2 = createDisclosureContext(defaultedProps.contextId);
const InternalDisclosureContext2 = createInternalDisclosureContext(defaultedProps.contextId);
return createComponent(DisclosureContext2.Provider, {
value: {
expanded,
setExpanded,
collapseBehavior: () => defaultedProps.collapseBehavior,
preventInitialContentAnimation: () => defaultedProps.preventInitialContentAnimation,
disclosureId: () => defaultedProps.disclosureId,
contentPresent,
contentRef,
contentSize
},
get children() {
return createComponent(InternalDisclosureContext2.Provider, {
value: {
expanded,
setExpanded,
collapseBehavior: () => defaultedProps.collapseBehavior,
preventInitialContentAnimation: () => defaultedProps.preventInitialContentAnimation,
disclosureId: () => defaultedProps.disclosureId,
contentPresent,
contentRef,
setContentRef,
contentSize
},
get children() {
return untrack(() => resolveChildren());
}
});
}
});
});
return memoizedDisclosureRoot;
};
var Root_default = DisclosureRoot;
var DisclosureTrigger = (props) => {
const [localProps, otherProps] = splitProps(props, ["contextId", "onClick"]);
const context = createMemo(() => useInternalDisclosureContext(localProps.contextId));
const onClick = (e) => {
!callEventHandler(localProps.onClick, e) && context().setExpanded((expanded) => !expanded);
};
return createComponent(DynamicButton, mergeProps$1({
onClick,
get ["aria-controls"]() {
return context().disclosureId();
},
get ["aria-expanded"]() {
return context().expanded() ? "true" : "false";
},
get ["data-collapsed"]() {
return dataIf(!context().expanded());
},
get ["data-expanded"]() {
return dataIf(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 };