Package entry point and lint fixes

Adds packages/core/src/index.ts as the convenience re-export barrel for
all Dialog parts, Presence, Portal, VisuallyHidden, createFocusTrap,
createScrollLock, and createDismiss. Also resolves pre-existing biome
formatter and noNonNullAssertion violations across five files so CI passes.
This commit is contained in:
Mats Bosson 2026-03-29 06:01:41 +07:00
parent a25244840b
commit 7dd8615757
7 changed files with 53 additions and 17 deletions

View File

@ -0,0 +1,31 @@
// packages/core/src/index.ts
// Main entry — re-exports everything for convenience.
// Prefer sub-path imports (e.g. "pettyui/dialog") for tree-shaking.
export { Dialog } from "./components/dialog/index";
export type { DialogRootProps } from "./components/dialog/dialog-root";
export type { DialogContentProps } from "./components/dialog/dialog-content";
export type { DialogTitleProps } from "./components/dialog/dialog-title";
export type { DialogDescriptionProps } from "./components/dialog/dialog-description";
export type { DialogTriggerProps } from "./components/dialog/dialog-trigger";
export type { DialogCloseProps } from "./components/dialog/dialog-close";
export type { DialogPortalProps } from "./components/dialog/dialog-portal";
export type { DialogOverlayProps } from "./components/dialog/dialog-overlay";
export { Presence } from "./utilities/presence/index";
export type { PresenceProps, PresenceChildProps } from "./utilities/presence/index";
export { Portal } from "./utilities/portal/index";
export type { PortalProps } from "./utilities/portal/index";
export { VisuallyHidden } from "./utilities/visually-hidden/index";
export type { VisuallyHiddenProps } from "./utilities/visually-hidden/index";
export { createFocusTrap } from "./utilities/focus-trap/index";
export type { FocusTrap } from "./utilities/focus-trap/index";
export { createScrollLock } from "./utilities/scroll-lock/index";
export type { ScrollLock } from "./utilities/scroll-lock/index";
export { createDismiss } from "./utilities/dismiss/index";
export type { CreateDismissOptions, Dismiss } from "./utilities/dismiss/index";

View File

@ -19,9 +19,7 @@ export interface DisclosureState {
* Tooltip, Collapsible, etc.). Wraps createControllableSignal with * Tooltip, Collapsible, etc.). Wraps createControllableSignal with
* convenience open/close/toggle methods. * convenience open/close/toggle methods.
*/ */
export function createDisclosureState( export function createDisclosureState(options: CreateDisclosureStateOptions): DisclosureState {
options: CreateDisclosureStateOptions,
): DisclosureState {
const [isOpen, setIsOpen] = createControllableSignal<boolean>({ const [isOpen, setIsOpen] = createControllableSignal<boolean>({
value: () => options.open, value: () => options.open,
defaultValue: () => options.defaultOpen ?? false, defaultValue: () => options.defaultOpen ?? false,

View File

@ -36,8 +36,8 @@ export function createFocusTrap(getContainer: Accessor<HTMLElement | null>): Foc
return; return;
} }
const first = focusable[0]!; const first = focusable[0] as HTMLElement;
const last = focusable[focusable.length - 1]!; const last = focusable[focusable.length - 1] as HTMLElement;
if (e.shiftKey) { if (e.shiftKey) {
if (document.activeElement === first) { if (document.activeElement === first) {

View File

@ -17,9 +17,5 @@ export function Portal(props: PortalProps): JSX.Element {
return <>{props.children}</>; return <>{props.children}</>;
} }
return ( return <SolidPortal mount={props.target ?? document.body}>{props.children}</SolidPortal>;
<SolidPortal mount={props.target ?? document.body}>
{props.children}
</SolidPortal>
);
} }

View File

@ -75,12 +75,12 @@ export function Presence(props: PresenceProps): JSX.Element {
// Wrap in a function so Show evaluates children lazily (only when mounted). // Wrap in a function so Show evaluates children lazily (only when mounted).
return ( return (
<Show when={shouldMount()}> <Show when={shouldMount()}>
{( {
() => (() =>
typeof props.children === "function" typeof props.children === "function"
? (props.children as (p: PresenceChildProps) => JSX.Element)(childProps) ? (props.children as (p: PresenceChildProps) => JSX.Element)(childProps)
: props.children : props.children) as unknown as JSX.Element
) as unknown as JSX.Element} }
</Show> </Show>
); );
} }

View File

@ -96,7 +96,12 @@ describe("createFocusTrap", () => {
const trap = createFocusTrap(() => container); const trap = createFocusTrap(() => container);
trap.activate(); trap.activate();
// button1 is already focused after activate // button1 is already focused after activate
const event = new KeyboardEvent("keydown", { key: "Tab", shiftKey: true, bubbles: true, cancelable: true }); const event = new KeyboardEvent("keydown", {
key: "Tab",
shiftKey: true,
bubbles: true,
cancelable: true,
});
document.dispatchEvent(event); document.dispatchEvent(event);
expect(document.activeElement).toBe(button2); expect(document.activeElement).toBe(button2);
trap.deactivate(); trap.deactivate();
@ -138,7 +143,9 @@ describe("createFocusTrap", () => {
// Tab from the only element — Tab wraps back to itself (first === last) // Tab from the only element — Tab wraps back to itself (first === last)
// What matters is it doesn't fire twice causing erratic behaviour // What matters is it doesn't fire twice causing erratic behaviour
expect(() => { expect(() => {
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true })); document.dispatchEvent(
new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true }),
);
}).not.toThrow(); }).not.toThrow();
trap.deactivate(); trap.deactivate();
dispose(); dispose();

View File

@ -4,7 +4,11 @@ import { Portal } from "../../src/utilities/portal/portal";
describe("Portal", () => { describe("Portal", () => {
it("renders children into document.body by default", () => { it("renders children into document.body by default", () => {
render(() => <Portal><div data-testid="portal-content">hello</div></Portal>); render(() => (
<Portal>
<div data-testid="portal-content">hello</div>
</Portal>
));
// Content should be in document.body, not the render container // Content should be in document.body, not the render container
expect(document.body.querySelector("[data-testid='portal-content']")).toBeTruthy(); expect(document.body.querySelector("[data-testid='portal-content']")).toBeTruthy();
}); });