{"version":3,"file":"ErrorBoundary.mjs","names":[],"sources":["../src/ErrorBoundary.tsx"],"sourcesContent":["'use client'\nimport {\n  Component,\n  type ComponentProps,\n  type ComponentType,\n  type ErrorInfo,\n  type ForwardRefExoticComponent,\n  type ForwardedRef,\n  type FunctionComponent,\n  type PropsWithChildren,\n  type ReactNode,\n  createContext,\n  forwardRef,\n  useContext,\n  useImperativeHandle,\n  useMemo,\n  useRef,\n  useState,\n} from 'react'\nimport { ErrorBoundaryGroupContext } from './ErrorBoundaryGroup'\nimport {\n  Message_useErrorBoundaryFallbackProps_this_hook_should_be_called_in_ErrorBoundary_props_fallback,\n  Message_useErrorBoundary_this_hook_should_be_called_in_ErrorBoundary_props_children,\n  SuspensiveError,\n} from './models/SuspensiveError'\nimport type { ConstructorType } from './utility-types/ConstructorType'\nimport type { PropsWithoutChildren } from './utility-types/PropsWithoutChildren'\nimport { hasResetKeysChanged } from './utils/hasResetKeysChanged'\n\ninterface ErrorBoundaryHandle {\n  /**\n   * when you want to reset caught error, you can use this reset\n   */\n  reset: () => void\n}\n\nexport interface ErrorBoundaryFallbackProps<TError extends Error = Error> extends ErrorBoundaryHandle {\n  /**\n   * when ErrorBoundary catch error, you can use this error\n   */\n  error: TError\n}\n\ntype ErrorTypeGuard<TError extends Error> = (error: Error) => error is TError\ntype ErrorValidator = (error: Error) => boolean\n\ntype ErrorMatcher = boolean | ConstructorType<Error> | ErrorTypeGuard<Error> | ErrorValidator\n\ntype InferErrorByErrorMatcher<TErrorMatcher extends ErrorMatcher> =\n  TErrorMatcher extends ConstructorType<infer TErrorOfConstructorType extends Error>\n    ? TErrorOfConstructorType\n    : TErrorMatcher extends ErrorTypeGuard<infer TErrorOfTypeGuard extends Error>\n      ? TErrorOfTypeGuard\n      : Error\n\ntype ShouldCatch = ErrorMatcher | [ErrorMatcher, ...ErrorMatcher[]]\n/**\n * Main type inference from shouldCatch\n */\ntype InferError<TShouldCatch extends ShouldCatch> = TShouldCatch extends readonly ErrorMatcher[]\n  ? InferErrorByErrorMatcher<TShouldCatch[number]> extends never\n    ? Error\n    : InferErrorByErrorMatcher<TShouldCatch[number]>\n  : TShouldCatch extends ErrorMatcher\n    ? InferErrorByErrorMatcher<TShouldCatch>\n    : Error\n\nconst matchError = (errorMatcher: ErrorMatcher, error: Error): error is InferError<typeof errorMatcher> => {\n  if (typeof errorMatcher === 'boolean') {\n    return errorMatcher\n  }\n  if (typeof errorMatcher === 'function') {\n    // 1. Native Error constructor: prototype chain is intact\n    try {\n      if (errorMatcher === Error || errorMatcher.prototype instanceof Error) {\n        return error instanceof errorMatcher\n      }\n    } catch {\n      // If accessing prototype throws, it's not a constructor\n    }\n    // 2. Transpiled Error constructor: prototype chain is broken but instanceof still works\n    try {\n      if (error instanceof errorMatcher) {\n        return true\n      }\n    } catch {\n      // instanceof can throw if prototype is not an object (e.g., arrow functions)\n    }\n    // 3. Validator / type-guard function\n    try {\n      return (errorMatcher as ErrorValidator | ErrorTypeGuard<InferError<typeof errorMatcher>>)(error)\n    } catch {\n      return false\n    }\n  }\n  return false\n}\n\nconst shouldCatchError = <TShouldCatch extends ShouldCatch>(\n  shouldCatch: TShouldCatch | true,\n  error: Error\n): error is InferError<TShouldCatch> =>\n  Array.isArray(shouldCatch)\n    ? shouldCatch.some((errorMatcher) => matchError(errorMatcher, error))\n    : matchError(shouldCatch, error)\n\nexport type ErrorBoundaryProps<TShouldCatch extends ShouldCatch = true> = PropsWithChildren<{\n  /**\n   * an array of elements for the ErrorBoundary to check each render. If any of those elements change between renders, then the ErrorBoundary will reset the state which will re-render the children\n   */\n  resetKeys?: unknown[]\n  /**\n   * when ErrorBoundary is reset by resetKeys or fallback's props.reset, onReset will be triggered\n   */\n  onReset?: () => void\n  /**\n   * when ErrorBoundary catch error, onError will be triggered\n   */\n  onError?: (error: InferError<TShouldCatch>, info: ErrorInfo) => void\n  /**\n   * when ErrorBoundary catch error, fallback will be render instead of children\n   */\n  fallback: ReactNode | FunctionComponent<ErrorBoundaryFallbackProps<InferError<TShouldCatch>>>\n  /**\n   * determines whether the ErrorBoundary should catch errors based on conditions\n   * @default true\n   */\n  shouldCatch?: TShouldCatch\n}>\n\ntype ErrorBoundaryState =\n  | {\n      isError: true\n      error: Error\n    }\n  | {\n      isError: false\n      error: null\n    }\n\nconst initialErrorBoundaryState: ErrorBoundaryState = {\n  isError: false,\n  error: null,\n}\n\nclass BaseErrorBoundary<TShouldCatch extends ShouldCatch> extends Component<\n  ErrorBoundaryProps<TShouldCatch>,\n  ErrorBoundaryState\n> {\n  static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n    return { isError: true, error }\n  }\n\n  state = initialErrorBoundaryState\n\n  componentDidUpdate(prevProps: ErrorBoundaryProps<TShouldCatch>, prevState: ErrorBoundaryState) {\n    const { isError } = this.state\n    const { resetKeys } = this.props\n    if (isError && prevState.isError && hasResetKeysChanged(prevProps.resetKeys, resetKeys)) {\n      this.reset()\n    }\n  }\n\n  componentDidCatch(error: InferError<TShouldCatch>, info: ErrorInfo) {\n    this.props.onError?.(error, info)\n  }\n\n  reset = () => {\n    this.props.onReset?.()\n    this.setState(initialErrorBoundaryState)\n  }\n\n  render() {\n    const { children, fallback, shouldCatch = true } = this.props\n    const { isError, error } = this.state\n\n    let childrenOrFallback = children\n\n    if (isError) {\n      if (error instanceof SuspensiveError) {\n        throw error\n      }\n      if (error instanceof ErrorInFallback) {\n        throw error.originalError\n      }\n      if (!shouldCatchError(shouldCatch, error)) {\n        throw error\n      }\n\n      if (typeof fallback === 'undefined') {\n        if (process.env.NODE_ENV === 'development') {\n          console.error('ErrorBoundary of @suspensive/react requires a defined fallback')\n        }\n        throw error\n      }\n\n      const Fallback = fallback\n      childrenOrFallback = (\n        <FallbackBoundary>\n          {typeof Fallback === 'function' ? <Fallback error={error} reset={this.reset} /> : Fallback}\n        </FallbackBoundary>\n      )\n    }\n\n    return (\n      <ErrorBoundaryContext.Provider value={{ ...this.state, reset: this.reset }}>\n        {childrenOrFallback}\n      </ErrorBoundaryContext.Provider>\n    )\n  }\n}\n\nclass ErrorInFallback extends Error {\n  originalError: Error\n  constructor(originalError: Error) {\n    super()\n    this.originalError = originalError\n  }\n}\n\nclass FallbackBoundary extends Component<{ children: ReactNode }> {\n  componentDidCatch(originalError: Error) {\n    throw originalError instanceof SuspensiveError ? originalError : new ErrorInFallback(originalError)\n  }\n  render() {\n    return this.props.children\n  }\n}\n\n/**\n * This component provides a simple and reusable wrapper that you can use to wrap around your components. Any rendering errors in your components hierarchy can then be gracefully handled.\n * @see {@link https://suspensive.org/docs/react/ErrorBoundary Suspensive Docs}\n */\nexport const ErrorBoundary = Object.assign(\n  forwardRef(function ErrorBoundary<TShouldCatch extends ShouldCatch>(\n    props: ErrorBoundaryProps<TShouldCatch>,\n    ref: ForwardedRef<ErrorBoundaryHandle>\n  ) {\n    const { fallback, children, onError, onReset, resetKeys, shouldCatch } = props\n    const group = useContext(ErrorBoundaryGroupContext) ?? { resetKey: 0 }\n    const baseErrorBoundaryRef = useRef<BaseErrorBoundary<TShouldCatch>>(null)\n    useImperativeHandle(ref, () => ({\n      reset: () => baseErrorBoundaryRef.current?.reset(),\n    }))\n\n    return (\n      <BaseErrorBoundary<TShouldCatch>\n        shouldCatch={shouldCatch}\n        fallback={fallback}\n        onError={onError}\n        onReset={onReset}\n        resetKeys={[group.resetKey, ...(resetKeys || [])]}\n        ref={baseErrorBoundaryRef}\n      >\n        {children}\n      </BaseErrorBoundary>\n    )\n  }) as {\n    <TShouldCatch extends ShouldCatch>(\n      props: ErrorBoundaryProps<TShouldCatch> & React.RefAttributes<ErrorBoundaryHandle>\n    ): ReturnType<ForwardRefExoticComponent<ErrorBoundaryProps<TShouldCatch>>>\n  },\n  {\n    displayName: 'ErrorBoundary',\n    with: <\n      TProps extends ComponentProps<ComponentType> = Record<string, never>,\n      TShouldCatch extends ShouldCatch = ShouldCatch,\n    >(\n      errorBoundaryProps: PropsWithoutChildren<ErrorBoundaryProps<TShouldCatch>>,\n      Component: ComponentType<TProps>\n    ) =>\n      Object.assign(\n        (props: TProps) => (\n          <ErrorBoundary<TShouldCatch> {...errorBoundaryProps}>\n            <Component {...props} />\n          </ErrorBoundary>\n        ),\n        { displayName: `${ErrorBoundary.displayName}.with(${Component.displayName || Component.name || 'Component'})` }\n      ),\n    Consumer: ({ children }: { children: (errorBoundary: ReturnType<typeof useErrorBoundary>) => ReactNode }) => (\n      <>{children(useErrorBoundary())}</>\n    ),\n  }\n)\n\nconst ErrorBoundaryContext = Object.assign(createContext<(ErrorBoundaryHandle & ErrorBoundaryState) | null>(null), {\n  displayName: 'ErrorBoundaryContext',\n})\n\n/**\n * This hook provides a simple and reusable wrapper that you can use to wrap around your components. Any rendering errors in your components hierarchy can then be gracefully handled.\n * @see {@link https://suspensive.org/docs/react/ErrorBoundary#useerrorboundary Suspensive Docs}\n */\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\nexport const useErrorBoundary = <TError extends Error = Error>() => {\n  const [state, setState] = useState<ErrorBoundaryState>({\n    isError: false,\n    error: null,\n  })\n  if (state.isError) {\n    throw state.error\n  }\n\n  const errorBoundary = useContext(ErrorBoundaryContext)\n  SuspensiveError.assert(\n    errorBoundary != null && !errorBoundary.isError,\n    Message_useErrorBoundary_this_hook_should_be_called_in_ErrorBoundary_props_children\n  )\n\n  return useMemo(\n    () => ({\n      setError: (error: TError) => setState({ isError: true, error }),\n    }),\n    []\n  )\n}\n\n/**\n * This hook allows you to access the reset method and error objects without prop drilling.\n * @see {@link https://suspensive.org/docs/react/ErrorBoundary#useerrorboundaryfallbackprops Suspensive Docs}\n */\nexport const useErrorBoundaryFallbackProps = <TError extends Error = Error>(): ErrorBoundaryFallbackProps<TError> => {\n  const errorBoundary = useContext(ErrorBoundaryContext)\n  SuspensiveError.assert(\n    errorBoundary != null && errorBoundary.isError,\n    Message_useErrorBoundaryFallbackProps_this_hook_should_be_called_in_ErrorBoundary_props_fallback\n  )\n\n  return useMemo(\n    () => ({\n      error: errorBoundary.error as TError,\n      reset: errorBoundary.reset,\n    }),\n    [errorBoundary.error, errorBoundary.reset]\n  )\n}\n"],"mappings":"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"}