Dialog component
Implements DialogRoot, Content, Title, Description, Trigger, Close, Portal, and Overlay parts with full context wiring, focus trap, scroll lock, and dismiss. Also fixes pre-existing TS6 exactOptionalPropertyTypes errors in create-disclosure-state and presence, and silences TS6.0 deprecation warnings via ignoreDeprecations.
This commit is contained in:
parent
4e711d8f5d
commit
69068fbee9
28
packages/core/src/components/dialog/dialog-close.tsx
Normal file
28
packages/core/src/components/dialog/dialog-close.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// packages/core/src/components/dialog/dialog-close.tsx
|
||||||
|
import type { Component, JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import { Dynamic } from "solid-js/web";
|
||||||
|
import { useInternalDialogContext } from "./dialog-context";
|
||||||
|
|
||||||
|
export interface DialogCloseProps extends JSX.HTMLAttributes<HTMLButtonElement> {
|
||||||
|
as?: string | Component;
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closes the dialog when clicked. Supports polymorphic rendering via `as`. */
|
||||||
|
export function DialogClose(props: DialogCloseProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["as", "children", "onClick"]);
|
||||||
|
const ctx = useInternalDialogContext();
|
||||||
|
|
||||||
|
const handleClick: JSX.EventHandler<HTMLElement, MouseEvent> = (e) => {
|
||||||
|
if (typeof local.onClick === "function")
|
||||||
|
local.onClick(e as MouseEvent & { currentTarget: HTMLButtonElement; target: Element });
|
||||||
|
ctx.setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dynamic component={local.as ?? "button"} onClick={handleClick} {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</Dynamic>
|
||||||
|
);
|
||||||
|
}
|
||||||
76
packages/core/src/components/dialog/dialog-content.tsx
Normal file
76
packages/core/src/components/dialog/dialog-content.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// packages/core/src/components/dialog/dialog-content.tsx
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { Show, onCleanup, onMount, splitProps } from "solid-js";
|
||||||
|
import { createDismiss } from "../../utilities/dismiss/create-dismiss";
|
||||||
|
import { createFocusTrap } from "../../utilities/focus-trap/create-focus-trap";
|
||||||
|
import { Portal } from "../../utilities/portal/portal";
|
||||||
|
import { createScrollLock } from "../../utilities/scroll-lock/create-scroll-lock";
|
||||||
|
import { useInternalDialogContext } from "./dialog-context";
|
||||||
|
|
||||||
|
export interface DialogContentProps extends JSX.DialogHtmlAttributes<HTMLDialogElement> {
|
||||||
|
/**
|
||||||
|
* Called when auto-focus fires on open. Call event.preventDefault() to prevent default.
|
||||||
|
*/
|
||||||
|
onOpenAutoFocus?: (event: Event) => void;
|
||||||
|
/**
|
||||||
|
* Called when auto-focus fires on close. Call event.preventDefault() to prevent default.
|
||||||
|
*/
|
||||||
|
onCloseAutoFocus?: (event: Event) => void;
|
||||||
|
/** Keep mounted even when closed (for animation control). */
|
||||||
|
forceMount?: boolean;
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog content panel. Portals to body, manages focus trap, scroll lock, and dismiss.
|
||||||
|
* Only renders when open (unless forceMount is set).
|
||||||
|
*/
|
||||||
|
export function DialogContent(props: DialogContentProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, [
|
||||||
|
"children",
|
||||||
|
"onOpenAutoFocus",
|
||||||
|
"onCloseAutoFocus",
|
||||||
|
"forceMount",
|
||||||
|
]);
|
||||||
|
const ctx = useInternalDialogContext();
|
||||||
|
let contentRef: HTMLDialogElement | undefined;
|
||||||
|
|
||||||
|
const focusTrap = createFocusTrap(() => contentRef ?? null);
|
||||||
|
const scrollLock = createScrollLock();
|
||||||
|
const dismiss = createDismiss({
|
||||||
|
getContainer: () => contentRef ?? null,
|
||||||
|
onDismiss: () => ctx.setOpen(false),
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (ctx.isOpen() && ctx.modal()) {
|
||||||
|
focusTrap.activate();
|
||||||
|
scrollLock.lock();
|
||||||
|
dismiss.attach();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
focusTrap.deactivate();
|
||||||
|
scrollLock.unlock();
|
||||||
|
dismiss.detach();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={local.forceMount || ctx.isOpen()}>
|
||||||
|
<Portal>
|
||||||
|
<dialog
|
||||||
|
ref={contentRef}
|
||||||
|
id={ctx.contentId()}
|
||||||
|
aria-modal={ctx.modal() || undefined}
|
||||||
|
aria-labelledby={ctx.titleId()}
|
||||||
|
aria-describedby={ctx.descriptionId()}
|
||||||
|
data-state={ctx.isOpen() ? "open" : "closed"}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</dialog>
|
||||||
|
</Portal>
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
packages/core/src/components/dialog/dialog-description.tsx
Normal file
24
packages/core/src/components/dialog/dialog-description.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// packages/core/src/components/dialog/dialog-description.tsx
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { createUniqueId, onCleanup, onMount, splitProps } from "solid-js";
|
||||||
|
import { useInternalDialogContext } from "./dialog-context";
|
||||||
|
|
||||||
|
export interface DialogDescriptionProps extends JSX.HTMLAttributes<HTMLParagraphElement> {
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renders as p and registers its ID for aria-describedby on Dialog.Content. */
|
||||||
|
export function DialogDescription(props: DialogDescriptionProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
const ctx = useInternalDialogContext();
|
||||||
|
const id = createUniqueId();
|
||||||
|
|
||||||
|
onMount(() => ctx.setDescriptionId(id));
|
||||||
|
onCleanup(() => ctx.setDescriptionId(undefined));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p id={id} {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
packages/core/src/components/dialog/dialog-overlay.tsx
Normal file
20
packages/core/src/components/dialog/dialog-overlay.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// packages/core/src/components/dialog/dialog-overlay.tsx
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { Show, splitProps } from "solid-js";
|
||||||
|
import { useInternalDialogContext } from "./dialog-context";
|
||||||
|
|
||||||
|
export interface DialogOverlayProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||||
|
forceMount?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Semi-transparent overlay rendered behind dialog content. */
|
||||||
|
export function DialogOverlay(props: DialogOverlayProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["forceMount"]);
|
||||||
|
const ctx = useInternalDialogContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={local.forceMount || ctx.isOpen()}>
|
||||||
|
<div aria-hidden="true" data-state={ctx.isOpen() ? "open" : "closed"} {...rest} />
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
packages/core/src/components/dialog/dialog-portal.tsx
Normal file
20
packages/core/src/components/dialog/dialog-portal.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// packages/core/src/components/dialog/dialog-portal.tsx
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import { Portal } from "../../utilities/portal/portal";
|
||||||
|
|
||||||
|
export interface DialogPortalProps {
|
||||||
|
/** Override the portal target container. */
|
||||||
|
target?: Element | null;
|
||||||
|
children: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renders children into a portal (defaults to document.body). */
|
||||||
|
export function DialogPortal(props: DialogPortalProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["target", "children"]);
|
||||||
|
return local.target !== undefined ? (
|
||||||
|
<Portal target={local.target}>{local.children}</Portal>
|
||||||
|
) : (
|
||||||
|
<Portal>{local.children}</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
72
packages/core/src/components/dialog/dialog-root.tsx
Normal file
72
packages/core/src/components/dialog/dialog-root.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// packages/core/src/components/dialog/dialog-root.tsx
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { createSignal } from "solid-js";
|
||||||
|
import {
|
||||||
|
type CreateDisclosureStateOptions,
|
||||||
|
createDisclosureState,
|
||||||
|
} from "../../primitives/create-disclosure-state";
|
||||||
|
import { createRegisterId } from "../../primitives/create-register-id";
|
||||||
|
import {
|
||||||
|
DialogContextProvider,
|
||||||
|
InternalDialogContextProvider,
|
||||||
|
type InternalDialogContextValue,
|
||||||
|
} from "./dialog-context";
|
||||||
|
|
||||||
|
export interface DialogRootProps {
|
||||||
|
/** Controlled open state. */
|
||||||
|
open?: boolean;
|
||||||
|
/** Default open state (uncontrolled). */
|
||||||
|
defaultOpen?: boolean;
|
||||||
|
/** Called when open state should change. */
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
/**
|
||||||
|
* Whether the dialog blocks outside interaction and traps focus.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
modal?: boolean;
|
||||||
|
children: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root component. Manages open state, provides context to all Dialog parts.
|
||||||
|
* Renders no DOM elements itself.
|
||||||
|
*/
|
||||||
|
export function DialogRoot(props: DialogRootProps): JSX.Element {
|
||||||
|
const disclosure = createDisclosureState({
|
||||||
|
get open() {
|
||||||
|
return props.open;
|
||||||
|
},
|
||||||
|
get defaultOpen() {
|
||||||
|
return props.defaultOpen;
|
||||||
|
},
|
||||||
|
get onOpenChange() {
|
||||||
|
return props.onOpenChange;
|
||||||
|
},
|
||||||
|
} as CreateDisclosureStateOptions);
|
||||||
|
|
||||||
|
const contentId = `pettyui-dialog-${Math.random().toString(36).slice(2, 9)}`;
|
||||||
|
const [titleId, setTitleId] = createRegisterId();
|
||||||
|
const [descriptionId, setDescriptionId] = createRegisterId();
|
||||||
|
const [hasTrigger, setHasTrigger] = createSignal(false);
|
||||||
|
|
||||||
|
const internalCtx: InternalDialogContextValue = {
|
||||||
|
isOpen: disclosure.isOpen,
|
||||||
|
setOpen: (open) => (open ? disclosure.open() : disclosure.close()),
|
||||||
|
modal: () => props.modal ?? true,
|
||||||
|
contentId: () => contentId,
|
||||||
|
titleId,
|
||||||
|
setTitleId,
|
||||||
|
descriptionId,
|
||||||
|
setDescriptionId,
|
||||||
|
hasTrigger,
|
||||||
|
setHasTrigger,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InternalDialogContextProvider value={internalCtx}>
|
||||||
|
<DialogContextProvider value={{ open: disclosure.isOpen, modal: () => props.modal ?? true }}>
|
||||||
|
{props.children}
|
||||||
|
</DialogContextProvider>
|
||||||
|
</InternalDialogContextProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
packages/core/src/components/dialog/dialog-title.tsx
Normal file
24
packages/core/src/components/dialog/dialog-title.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// packages/core/src/components/dialog/dialog-title.tsx
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { createUniqueId, onCleanup, onMount, splitProps } from "solid-js";
|
||||||
|
import { useInternalDialogContext } from "./dialog-context";
|
||||||
|
|
||||||
|
export interface DialogTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renders as h2 and registers its ID for aria-labelledby on Dialog.Content. */
|
||||||
|
export function DialogTitle(props: DialogTitleProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
const ctx = useInternalDialogContext();
|
||||||
|
const id = createUniqueId();
|
||||||
|
|
||||||
|
onMount(() => ctx.setTitleId(id));
|
||||||
|
onCleanup(() => ctx.setTitleId(undefined));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<h2 id={id} {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
}
|
||||||
42
packages/core/src/components/dialog/dialog-trigger.tsx
Normal file
42
packages/core/src/components/dialog/dialog-trigger.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// packages/core/src/components/dialog/dialog-trigger.tsx
|
||||||
|
import type { Component, JSX } from "solid-js";
|
||||||
|
import { mergeProps, splitProps } from "solid-js";
|
||||||
|
import { Dynamic } from "solid-js/web";
|
||||||
|
import { useInternalDialogContext } from "./dialog-context";
|
||||||
|
|
||||||
|
export interface DialogTriggerProps
|
||||||
|
extends Omit<JSX.HTMLAttributes<HTMLButtonElement>, "children"> {
|
||||||
|
/** Render as a different element or component. */
|
||||||
|
as?: string | Component;
|
||||||
|
children?: JSX.Element | ((props: JSX.HTMLAttributes<HTMLElement>) => JSX.Element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Opens the dialog when clicked. Supports polymorphic rendering via `as` and children-as-function. */
|
||||||
|
export function DialogTrigger(props: DialogTriggerProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["as", "children", "onClick"]);
|
||||||
|
const ctx = useInternalDialogContext();
|
||||||
|
|
||||||
|
const handleClick: JSX.EventHandler<HTMLElement, MouseEvent> = (e) => {
|
||||||
|
if (typeof local.onClick === "function")
|
||||||
|
local.onClick(e as MouseEvent & { currentTarget: HTMLButtonElement; target: Element });
|
||||||
|
ctx.setOpen(!ctx.isOpen());
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerProps = mergeProps(rest, {
|
||||||
|
"aria-haspopup": "dialog" as const,
|
||||||
|
"aria-expanded": ctx.isOpen(),
|
||||||
|
"aria-controls": ctx.contentId(),
|
||||||
|
"data-state": ctx.isOpen() ? "open" : "closed",
|
||||||
|
onClick: handleClick,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof local.children === "function") {
|
||||||
|
return <>{local.children(triggerProps as JSX.HTMLAttributes<HTMLElement>)}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dynamic component={local.as ?? "button"} {...triggerProps}>
|
||||||
|
{local.children}
|
||||||
|
</Dynamic>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
packages/core/src/components/dialog/index.ts
Normal file
30
packages/core/src/components/dialog/index.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { DialogClose } from "./dialog-close";
|
||||||
|
import { DialogContent } from "./dialog-content";
|
||||||
|
import { useDialogContext } from "./dialog-context";
|
||||||
|
import { DialogDescription } from "./dialog-description";
|
||||||
|
import { DialogOverlay } from "./dialog-overlay";
|
||||||
|
import { DialogPortal } from "./dialog-portal";
|
||||||
|
// packages/core/src/components/dialog/index.ts
|
||||||
|
import { DialogRoot } from "./dialog-root";
|
||||||
|
import { DialogTitle } from "./dialog-title";
|
||||||
|
import { DialogTrigger } from "./dialog-trigger";
|
||||||
|
|
||||||
|
export const Dialog = Object.assign(DialogRoot, {
|
||||||
|
Content: DialogContent,
|
||||||
|
Title: DialogTitle,
|
||||||
|
Description: DialogDescription,
|
||||||
|
Trigger: DialogTrigger,
|
||||||
|
Close: DialogClose,
|
||||||
|
Portal: DialogPortal,
|
||||||
|
Overlay: DialogOverlay,
|
||||||
|
useContext: useDialogContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type { DialogRootProps } from "./dialog-root";
|
||||||
|
export type { DialogContentProps } from "./dialog-content";
|
||||||
|
export type { DialogTitleProps } from "./dialog-title";
|
||||||
|
export type { DialogDescriptionProps } from "./dialog-description";
|
||||||
|
export type { DialogTriggerProps } from "./dialog-trigger";
|
||||||
|
export type { DialogCloseProps } from "./dialog-close";
|
||||||
|
export type { DialogPortalProps } from "./dialog-portal";
|
||||||
|
export type { DialogOverlayProps } from "./dialog-overlay";
|
||||||
@ -25,7 +25,7 @@ export function createDisclosureState(
|
|||||||
const [isOpen, setIsOpen] = createControllableSignal<boolean>({
|
const [isOpen, setIsOpen] = createControllableSignal<boolean>({
|
||||||
value: () => options.open,
|
value: () => options.open,
|
||||||
defaultValue: () => options.defaultOpen ?? false,
|
defaultValue: () => options.defaultOpen ?? false,
|
||||||
onChange: options.onOpenChange,
|
...(options.onOpenChange !== undefined && { onChange: options.onOpenChange }),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -75,11 +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}
|
||||||
</Show>
|
</Show>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,65 @@
|
|||||||
|
// packages/core/tests/components/dialog/dialog-rendering.test.tsx
|
||||||
|
import { render, screen } from "@solidjs/testing-library";
|
||||||
|
import { createSignal } from "solid-js";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { Dialog } from "../../../src/components/dialog/index";
|
||||||
|
|
||||||
|
describe("Dialog rendering", () => {
|
||||||
|
it("renders children", () => {
|
||||||
|
render(() => (
|
||||||
|
<Dialog defaultOpen>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Title>Hello</Dialog.Title>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog>
|
||||||
|
));
|
||||||
|
expect(screen.getByText("Hello")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not render content when closed by default", () => {
|
||||||
|
render(() => (
|
||||||
|
<Dialog>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Title>Hidden</Dialog.Title>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog>
|
||||||
|
));
|
||||||
|
expect(screen.queryByText("Hidden")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders content when defaultOpen is true", () => {
|
||||||
|
render(() => (
|
||||||
|
<Dialog defaultOpen>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Title>Visible</Dialog.Title>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog>
|
||||||
|
));
|
||||||
|
expect(screen.getByText("Visible")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders content when controlled open is true", () => {
|
||||||
|
render(() => (
|
||||||
|
<Dialog open={true} onOpenChange={() => {}}>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Title>Controlled</Dialog.Title>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog>
|
||||||
|
));
|
||||||
|
expect(screen.getByText("Controlled")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("closes when controlled open is set to false", () => {
|
||||||
|
const [open, setOpen] = createSignal(true);
|
||||||
|
render(() => (
|
||||||
|
<Dialog open={open()} onOpenChange={setOpen}>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Title>Toggled</Dialog.Title>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog>
|
||||||
|
));
|
||||||
|
expect(screen.getByText("Toggled")).toBeTruthy();
|
||||||
|
setOpen(false);
|
||||||
|
expect(screen.queryByText("Toggled")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -13,6 +13,7 @@
|
|||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"esModuleInterop": false,
|
"esModuleInterop": false,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"ignoreDeprecations": "6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user