{"version":3,"file":"infinite-scroll.cjs","sources":["../src/infinite-scroll.ts"],"sourcesContent":["// @ts-nocheck\nimport h from \"@macrostrat/hyper\";\nimport update, { Spec } from \"immutability-helper\";\nimport React, { useReducer, useEffect, useRef, useCallback } from \"react\";\nimport { Spinner, NonIdealState } from \"@blueprintjs/core\";\nimport { APIParams, QueryParams } from \"./util/query-string\";\nimport { useInView } from \"react-intersection-observer\";\n\nimport { APIResultProps, useAPIActions } from \"./api\";\nimport { JSONView } from \"./util/json-view\";\nimport { IndexingProvider } from \"./api/indexing\";\n\ninterface ScrollState<T = object> {\n  items: T[];\n  scrollParams: APIParams;\n  count: number | null;\n  error?: any;\n  hasMore: boolean;\n  isLoadingPage: number | null;\n  pageIndex: number;\n}\n\ntype ScrollResponseItems<T> = Pick<\n  ScrollState<T>,\n  \"count\" | \"hasMore\" | \"items\"\n>;\n\nexport interface InfiniteScrollProps<T> extends Omit<\n  APIResultProps<T>,\n  \"params\"\n> {\n  getCount(r: T): number;\n  getNextParams(r: T, params: QueryParams): QueryParams;\n  getItems(r: T): any;\n  hasMore(res: T): boolean;\n  totalCount?: number;\n  // Only allow more restrictive parameter types\n  params: APIParams;\n  className?: string;\n  itemComponent?: React.ComponentType<{ data: T; index: number }>;\n  loadingPlaceholder?: React.ComponentType;\n  emptyPlaceholder?: React.ComponentType;\n  finishedPlaceholder?: React.ComponentType;\n  resultsComponent?: React.ComponentType<{ data: T[] }>;\n  perPage?: number;\n  startPage?: number;\n  initialItems?: T[];\n  delay?: number;\n  hideData?: boolean;\n}\n\ntype UpdateState<T> = { type: \"update-state\"; spec: Spec<ScrollState<T>> };\ntype LoadPage<T> = {\n  type: \"load-page\";\n  params: APIParams;\n  dispatch: Dispatch<T>;\n  callback<T>(action: LoadPage<T>): void;\n};\n\ntype ScrollAction<T> = UpdateState<T> | LoadPage<T>;\ntype Reducer<T> = (\n  state: ScrollState<T>,\n  action: ScrollAction<T>,\n) => ScrollState<T>;\ntype Dispatch<T> = (action: ScrollAction<T>) => void;\n\nfunction infiniteScrollReducer<T>(\n  state: ScrollState<T>,\n  action: ScrollAction<T>,\n) {\n  switch (action.type) {\n    case \"update-state\":\n      return update(state, action.spec);\n    case \"load-page\":\n      action.callback(action);\n      return update(state, {\n        // @ts-ignore\n        isLoadingPage: { $set: action.params?.page ?? 0 },\n      });\n  }\n}\n\nexport function InfiniteScroll(props) {\n  const {\n    hasMore,\n    children,\n    className,\n    loadMore,\n    offset = 0,\n    isLoading,\n    delay = 100,\n  } = props;\n  const { ref, inView } = useInView({\n    rootMargin: `0px 0px ${offset}px 0px`,\n    trackVisibility: true,\n    delay: delay >= 100 ? delay : 100,\n  });\n\n  // Only load more if not currently loading\n  const shouldLoadMore = hasMore && inView && !isLoading;\n\n  useEffect(() => {\n    if (shouldLoadMore) {\n      loadMore();\n    }\n  }, [shouldLoadMore, loadMore]);\n\n  return h(\"div.infinite-scroll-container\", { className }, [\n    children,\n    h(\"div.bottom-marker\", { ref, style: { padding: \"1px\" } }),\n  ]);\n}\n\nconst Placeholder = (props) => {\n  const {\n    loading,\n    title = \"No results yet\",\n    description = null,\n    ...rest\n  } = props;\n\n  return h(\"div.placeholder\", [\n    h(NonIdealState, {\n      icon: \"search-template\",\n      className: \"placeholder-inner\",\n      title,\n      description,\n      ...rest,\n    }),\n  ]);\n};\n\nexport const LoadingPlaceholder = (props: {\n  itemName?: string;\n  scrollParams?: APIParams;\n  pageIndex?: number | null;\n  loadedCount?: number;\n  totalCount?: number;\n  perPage?: number;\n}) => {\n  const { perPage = 10, loadedCount = 0, itemName = \"results\" } = props;\n  const { totalCount = 0 } = props;\n\n  let description = null;\n  if (totalCount != null) {\n    const loadedPages = Math.ceil(loadedCount / perPage);\n    const totalPages = Math.ceil(totalCount / perPage);\n    description = `Page ${loadedPages} of ${totalPages}`;\n  }\n\n  return h(Placeholder, {\n    icon: h(Spinner),\n    title: \"Loading \" + itemName,\n    description,\n  });\n};\n\nfunction EmptyPlaceholder() {\n  return h(Placeholder, {\n    icon: \"inbox\",\n    title: \"No results\",\n  });\n}\n\nfunction FinishedPlaceholder({ totalCount, ...rest }: { totalCount?: number }) {\n  const description = totalCount != null ? `${totalCount} total items` : null;\n  return h(Placeholder, {\n    icon: null,\n    title: \"No more results\",\n    description,\n    ...rest,\n  });\n}\n\nfunction InfiniteScrollView<T>(props: InfiniteScrollProps<T>) {\n  const {\n    route,\n    params,\n    opts,\n    placeholder = (p: APIPlaceholderProps) => h(Spinner),\n    className,\n    itemComponent = JSONView,\n    loadingPlaceholder = LoadingPlaceholder,\n    emptyPlaceholder = EmptyPlaceholder,\n    finishedPlaceholder = FinishedPlaceholder,\n    resultsComponent = \"div.results\",\n    perPage = 10,\n    startPage = 0,\n    initialItems = [],\n    delay,\n    hideData = false,\n  } = props;\n  const { get } = useAPIActions();\n  const {\n    getCount = () => null,\n    getNextParams = (response, params) => {\n      const lastPage = params.page ?? 0;\n      return { ...params, page: lastPage + 1 };\n    },\n    getItems = (d) => d,\n    hasMore = (d) => true,\n  } = props;\n\n  const initialState: ScrollState<T> = {\n    items: initialItems,\n    scrollParams: params,\n    count: null,\n    error: null,\n    hasMore: true,\n    isLoadingPage: null,\n    pageIndex: startPage,\n  };\n\n  const [state, dispatch] = useReducer<Reducer<T>>(\n    infiniteScrollReducer,\n    initialState,\n  );\n\n  const loadingRef = useRef(false);\n\n  const loadPage = useCallback(\n    async (action: LoadPage<T>) => {\n      if (loadingRef.current) return; // Prevent concurrent loads\n      loadingRef.current = true;\n\n      dispatch(action);\n\n      try {\n        const res = await get(route, action.params, opts);\n\n        const itemVals = getItems(res);\n        const nextParams = getNextParams(res, action.params);\n        const count = getCount(res);\n        const more = hasMore(res) && itemVals.length > 0 && nextParams != null;\n\n        action.dispatch({\n          type: \"update-state\",\n          spec: {\n            items: { $push: itemVals },\n            scrollParams: { $set: nextParams },\n            pageIndex: { $set: state.pageIndex + 1 },\n            count: { $set: count },\n            hasMore: { $set: more },\n            isLoadingPage: { $set: null },\n            error: { $set: null },\n          },\n        });\n      } catch (error) {\n        console.error(\"Error loading page:\", error);\n        action.dispatch({\n          type: \"update-state\",\n          spec: { error: { $set: error }, isLoadingPage: { $set: null } },\n        });\n      } finally {\n        loadingRef.current = false;\n      }\n    },\n    [\n      get,\n      route,\n      opts,\n      getItems,\n      getNextParams,\n      getCount,\n      hasMore,\n      state.pageIndex,\n    ],\n  );\n\n  const loadMore = useCallback(() => {\n    if (state.isLoadingPage !== null || !state.hasMore) return;\n    dispatch({\n      type: \"load-page\",\n      params: state.scrollParams,\n      dispatch,\n      callback: loadPage,\n    });\n  }, [state.isLoadingPage, state.hasMore, state.scrollParams, loadPage]);\n\n  const isInitialRender = useRef(true);\n\n  useEffect(() => {\n    if (isInitialRender.current) {\n      isInitialRender.current = false;\n      if (state.items.length === 0) {\n        loadMore();\n      }\n    }\n  }, [loadMore, state.items.length]);\n\n  if (state == null) return null;\n\n  const data = state.items;\n  const isLoading = state.isLoadingPage != null;\n  const isEmpty = (data.length === 0 && !isLoading) || hideData;\n  const isFinished = !state.hasMore && !isLoading;\n  const totalCount = props.totalCount ?? state.count;\n\n  return h(\n    InfiniteScroll,\n    {\n      pageStart: -1,\n      loadMore,\n      hasMore: state.hasMore && state.isLoadingPage == null,\n      loader: placeholder,\n      useWindow: true,\n      className,\n      delay,\n      isLoading,\n    },\n    [\n      h.if(isEmpty)(emptyPlaceholder),\n      h.if(!isEmpty)(IndexingProvider, { totalCount, indexOffset: 0 }, [\n        h(\n          resultsComponent,\n          { data },\n          data.map((d, i) => h(itemComponent, { key: i, data: d, index: i })),\n        ),\n        h.if(isLoading)(loadingPlaceholder, {\n          totalCount,\n          scrollParams: state.scrollParams,\n          pageIndex: state.pageIndex,\n          loadedCount: data.length,\n          perPage,\n        }),\n        h.if(isFinished)(finishedPlaceholder, { totalCount }),\n      ]),\n    ],\n  );\n}\n\nexport { InfiniteScrollView };\n"],"names":["update","useInView","useEffect","h","NonIdealState","Spinner","JSONView","useAPIActions","params","useReducer","useRef","useCallback","IndexingProvider"],"mappings":"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"}