UNPKG

react-overflow-slider

Version:

A customizable horizontal slider component for React with native scroll

1 lines 17.3 kB
{"version":3,"sources":["../src/slider/slider.tsx","../src/slider/button.tsx","../src/slider/utils/buttons-states.ts","../src/slider/utils/get-previous-offset.ts","../src/slider/utils/get-next-offset.ts","../src/slider/utils/smooth-scroll.ts","../src/slider/utils/scroll-to-first-visible.ts"],"sourcesContent":["import {\n Children,\n FC,\n ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport SliderButton from \"./button\";\nimport { TOverflowSlider, TButtonsState } from \"./slider.types\";\nimport {\n updateButtonsDisabled,\n updateButtonsVisibility,\n} from \"./utils/buttons-states\";\nimport { getPreviousOffset } from \"./utils/get-previous-offset\";\nimport { getNextOffset } from \"./utils/get-next-offset\";\nimport { smoothScroll } from \"./utils/smooth-scroll\";\nimport { scrollToFirstVisible } from \"./utils/scroll-to-first-visible\";\nimport \"./slider.style.scss\";\n\nconst OverflowSlider: FC<TOverflowSlider> = ({\n children,\n prevButton,\n nextButton,\n gap = 0,\n duration = 300,\n}) => {\n const scrollerRef = useRef<HTMLDivElement>(null);\n const itemsRef = useRef<(HTMLDivElement | null)[]>([]);\n const scrollWidth = useRef(0);\n const initialScrollDone = useRef(false);\n\n const [buttonsState, setButtonsState] = useState<TButtonsState>({\n prevButtonVisible: false,\n nextButtonVisible: false,\n prevButtonDisabled: false,\n nextButtonDisabled: false,\n });\n\n const checkShowButtons = useCallback(() => {\n updateButtonsVisibility(scrollerRef.current!, setButtonsState);\n }, []);\n\n const setDisabledButtons = useCallback(() => {\n updateButtonsDisabled(setButtonsState, true);\n setTimeout(\n () => updateButtonsDisabled(setButtonsState, false),\n duration + 50,\n );\n }, [duration]);\n\n const scrollToOffset = useCallback(\n (offset: number) => {\n smoothScroll(scrollerRef.current!, offset, duration);\n setDisabledButtons();\n },\n [duration, setDisabledButtons],\n );\n\n const handleScrollPrevious = useCallback(() => {\n const offset = getPreviousOffset(scrollerRef.current!, itemsRef.current);\n scrollToOffset(offset);\n }, [scrollToOffset]);\n\n const handleScrollNext = useCallback(() => {\n const offset = getNextOffset(scrollerRef.current!, itemsRef.current);\n scrollToOffset(offset);\n }, [scrollToOffset]);\n\n const scrollToFirstVisibleCallback = useCallback(() => {\n scrollToFirstVisible(itemsRef, scrollToOffset, initialScrollDone);\n }, [scrollToOffset]);\n\n useEffect(() => {\n const handleResize = checkShowButtons;\n window.addEventListener(\"resize\", handleResize);\n checkShowButtons();\n return () => window.removeEventListener(\"resize\", handleResize);\n }, [checkShowButtons]);\n\n useEffect(() => {\n const currentScrollWidth = scrollerRef.current?.scrollWidth ?? 0;\n if (currentScrollWidth !== scrollWidth.current) {\n scrollWidth.current = currentScrollWidth;\n checkShowButtons();\n scrollToFirstVisibleCallback();\n }\n }, [children, checkShowButtons, scrollToFirstVisibleCallback]);\n\n return (\n <div className=\"react-overflow-slider-container\">\n <div className=\"react-overflow-slider\">\n <div className=\"react-overflow-slider__btn-container\">\n <button\n type=\"button\"\n aria-label=\"Scroll to previous\"\n onClick={handleScrollPrevious}\n className={`react-overflow-slider__btn react-overflow-slider__btn--prev\n ${buttonsState.prevButtonDisabled ? \"react-overflow-slider__btn--disabled\" : \"\"}\n ${buttonsState.prevButtonVisible ? \"react-overflow-slider__btn--visible\" : \"\"}`}\n style={{ transition: `${duration}ms` }}\n >\n {prevButton || <SliderButton left />}\n </button>\n </div>\n\n <div\n ref={scrollerRef}\n className=\"react-overflow-slider__scroller\"\n onScroll={checkShowButtons}\n >\n <div className=\"react-overflow-slider__body\">\n {Children.map(children, (child: ReactNode, i) => {\n const isObject = typeof child === \"object\" && child !== null;\n const dataFirstVisible =\n isObject &&\n \"props\" in child &&\n child.props?.[\"data-first-visible\"];\n\n return (\n <div\n ref={(ref) => (itemsRef.current[i] = ref)}\n className=\"react-overflow-slider__item\"\n data-first-visible={dataFirstVisible}\n style={\n gap && i < children.length - 1 ? { paddingRight: gap } : {}\n }\n >\n {child}\n </div>\n );\n })}\n </div>\n </div>\n\n <div className=\"react-overflow-slider__btn-container\">\n <button\n type=\"button\"\n aria-label=\"Scroll to next\"\n onClick={handleScrollNext}\n className={`react-overflow-slider__btn react-overflow-slider__btn--next\n ${buttonsState.nextButtonDisabled ? \"react-overflow-slider__btn--disabled\" : \"\"}\n ${buttonsState.nextButtonVisible ? \"react-overflow-slider__btn--visible\" : \"\"}`}\n style={{ transition: `${duration}ms` }}\n >\n {nextButton || <SliderButton />}\n </button>\n </div>\n </div>\n </div>\n );\n};\n\nexport default OverflowSlider;\n","const SliderButton = ({ left }: { left?: boolean }) => {\n return (\n <div\n className={`react-overflow-slider-btn ${left ? \"react-overflow-slider-btn--left\" : \"\"} `}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n width=\"20\"\n height=\"20\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"8 4 16 12 8 20\" />\n </svg>\n </div>\n );\n};\n\nexport default SliderButton;\n","import { Dispatch, SetStateAction } from \"react\";\nimport { TButtonsState } from \"@/slider/slider.types\";\n\nexport function updateButtonsVisibility(\n scroller: HTMLDivElement,\n setButtonsState: Dispatch<SetStateAction<TButtonsState>>,\n) {\n const { offsetWidth, scrollLeft, scrollWidth } = scroller;\n setButtonsState((state) => ({\n ...state,\n prevButtonVisible: scrollLeft > 0,\n nextButtonVisible: scrollLeft + offsetWidth < scrollWidth,\n }));\n}\n\nexport function updateButtonsDisabled(\n setButtonsState: Dispatch<SetStateAction<TButtonsState>>,\n show: boolean,\n) {\n setButtonsState((state) => ({\n ...state,\n prevButtonDisabled: show,\n nextButtonDisabled: show,\n }));\n}\n","export function getPreviousOffset(\n scroller: HTMLDivElement,\n items: (HTMLDivElement | null)[],\n) {\n const findFirstVisibleItemIndex = (scrollLeft: number, widths: number[]) => {\n let first = 0,\n i = 0;\n while (first < scrollLeft) {\n first += widths[i];\n i++;\n }\n return i;\n };\n\n const findPreviousItemIndex = (\n visibleScroll: number,\n firstVisible: number,\n widths: number[],\n ) => {\n let i = firstVisible,\n possible = 0;\n if (widths[i - 1] >= visibleScroll) return i - 1;\n do {\n i--;\n possible += widths[i];\n } while (possible < visibleScroll && i > -1);\n return i + 1;\n };\n\n const scrollerVisibleWidth = scroller.offsetWidth;\n const scrollerScrollLeft = scroller.scrollLeft;\n const itemsWidths = items.map((item) => (item ? item.offsetWidth : 0));\n const firstVisible = findFirstVisibleItemIndex(\n scrollerScrollLeft,\n itemsWidths,\n );\n const prevItemIndex = findPreviousItemIndex(\n scrollerVisibleWidth,\n firstVisible,\n itemsWidths,\n );\n const itemsOffsetLeft = items.map((item) => (item ? item.offsetLeft : 0));\n\n return itemsOffsetLeft[prevItemIndex];\n}\n","export function getNextOffset(\n scroller: HTMLDivElement,\n items: (HTMLDivElement | null)[],\n) {\n const findNextItemIndex = (left: number, right: number, widths: number[]) => {\n let availableWidth = 0;\n let visibleWidth = 0;\n let i = 0;\n\n do {\n availableWidth += widths[i];\n i++;\n } while (right >= availableWidth && i < widths.length);\n\n i = i - 1;\n for (let j = 0; j < i; j++) {\n visibleWidth += widths[j];\n }\n\n return visibleWidth <= left ? i + 1 : i;\n // ⚠️ Potential Issue with Font-Based Width Calculations:\n // When elements have widths influenced by fonts, fractional values can cause rounding errors.\n // Example:\n // 79.68 + 81.73 rounds to 80 + 82 = 162,\n // but scrollLeft might return 161.41, which rounds to 161.\n // This mismatch can result in infinite loops or prevent scrolling to the next element.\n //\n // 💡 Solutions:\n // 1. Use universal fonts (e.g., system-native fonts, sans-serif) for consistent rendering across OS.\n // Note: Fonts like Arial may render inconsistently on macOS.\n // 2. Apply a tolerance range in the condition:\n // Change the condition to (visibleWidth - n <= left), where **n** is the pixel tolerance.\n // Increase **n** as the scroll moves further left to account for larger cumulative rounding errors.\n };\n\n const scrollerVisibleWidth = scroller.offsetWidth;\n const scrollerScrollLeft = scroller.scrollLeft;\n const scrollerScrollRight = scrollerScrollLeft + scrollerVisibleWidth;\n const itemsWidths = items.map((item) => (item ? item.offsetWidth : 0));\n const itemsOffsetLeft = items.map((item) => (item ? item.offsetLeft : 0));\n const nextItemIndex = findNextItemIndex(\n scrollerScrollLeft,\n scrollerScrollRight,\n itemsWidths,\n );\n const scrollerWidth = scroller.scrollWidth;\n const possibleScroll = scrollerWidth - scrollerVisibleWidth + 1;\n const nextItem =\n nextItemIndex !== items.length ? itemsOffsetLeft[nextItemIndex] : null;\n\n return nextItem !== null && possibleScroll >= nextItem\n ? nextItem\n : possibleScroll;\n}\n","const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);\n\nexport const smoothScroll = (\n scroller: HTMLDivElement,\n end: number,\n duration: number,\n) => {\n const start = scroller.scrollLeft;\n const startTime = performance.now();\n\n const step = (currentTime: number) => {\n const elapsed = (currentTime - startTime) / duration;\n const progress = Math.min(easeOutCubic(elapsed), 1);\n scroller.scrollLeft = start + (end - start) * progress;\n if (progress < 1) requestAnimationFrame(step);\n };\n\n requestAnimationFrame(step);\n};\n","export function findFirstVisibleIndex(items: (HTMLDivElement | null)[]) {\n return items.findIndex((item) => {\n if (!item) return false;\n if (item.dataset.firstVisible === \"true\") return true;\n if (item.firstElementChild?.getAttribute(\"data-first-visible\") === \"true\")\n return true;\n return Array.from(item.children).some(\n (el) => (el as HTMLElement).dataset.firstVisible === \"true\",\n );\n });\n}\n\nexport function scrollToFirstVisible(\n itemsRef: React.MutableRefObject<(HTMLDivElement | null)[]>,\n scrollToOffset: (offset: number) => void,\n initialScrollDone: React.MutableRefObject<boolean>,\n) {\n if (initialScrollDone.current) return;\n\n const index = findFirstVisibleIndex(itemsRef.current);\n\n if (index !== -1) {\n const targetOffset = itemsRef.current[index]?.offsetLeft ?? 0;\n scrollToOffset(targetOffset);\n initialScrollDone.current = true;\n }\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACQC;AAhBR,IAAM,eAAe,CAAC,EAAE,KAAK,MAA0B;AACrD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,6BAA6B,OAAO,oCAAoC,EAAE;AAAA,MAErF;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,SAAQ;AAAA,UACR,OAAM;AAAA,UACN,QAAO;AAAA,UACP,MAAK;AAAA,UACL,QAAO;AAAA,UACP,aAAY;AAAA,UACZ,eAAc;AAAA,UACd,gBAAe;AAAA,UAEf,8BAAC,cAAS,QAAO,kBAAiB;AAAA;AAAA,MACpC;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,iBAAQ;;;ACnBR,SAAS,wBACd,UACA,iBACA;AACA,QAAM,EAAE,aAAa,YAAY,YAAY,IAAI;AACjD,kBAAgB,CAAC,WAAW;AAAA,IAC1B,GAAG;AAAA,IACH,mBAAmB,aAAa;AAAA,IAChC,mBAAmB,aAAa,cAAc;AAAA,EAChD,EAAE;AACJ;AAEO,SAAS,sBACd,iBACA,MACA;AACA,kBAAgB,CAAC,WAAW;AAAA,IAC1B,GAAG;AAAA,IACH,oBAAoB;AAAA,IACpB,oBAAoB;AAAA,EACtB,EAAE;AACJ;;;ACxBO,SAAS,kBACd,UACA,OACA;AACA,QAAM,4BAA4B,CAAC,YAAoB,WAAqB;AAC1E,QAAI,QAAQ,GACV,IAAI;AACN,WAAO,QAAQ,YAAY;AACzB,eAAS,OAAO,CAAC;AACjB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,wBAAwB,CAC5B,eACAA,eACA,WACG;AACH,QAAI,IAAIA,eACN,WAAW;AACb,QAAI,OAAO,IAAI,CAAC,KAAK,cAAe,QAAO,IAAI;AAC/C,OAAG;AACD;AACA,kBAAY,OAAO,CAAC;AAAA,IACtB,SAAS,WAAW,iBAAiB,IAAI;AACzC,WAAO,IAAI;AAAA,EACb;AAEA,QAAM,uBAAuB,SAAS;AACtC,QAAM,qBAAqB,SAAS;AACpC,QAAM,cAAc,MAAM,IAAI,CAAC,SAAU,OAAO,KAAK,cAAc,CAAE;AACrE,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,EACF;AACA,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,kBAAkB,MAAM,IAAI,CAAC,SAAU,OAAO,KAAK,aAAa,CAAE;AAExE,SAAO,gBAAgB,aAAa;AACtC;;;AC5CO,SAAS,cACd,UACA,OACA;AACA,QAAM,oBAAoB,CAAC,MAAc,OAAe,WAAqB;AAC3E,QAAI,iBAAiB;AACrB,QAAI,eAAe;AACnB,QAAI,IAAI;AAER,OAAG;AACD,wBAAkB,OAAO,CAAC;AAC1B;AAAA,IACF,SAAS,SAAS,kBAAkB,IAAI,OAAO;AAE/C,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,sBAAgB,OAAO,CAAC;AAAA,IAC1B;AAEA,WAAO,gBAAgB,OAAO,IAAI,IAAI;AAAA,EAcxC;AAEA,QAAM,uBAAuB,SAAS;AACtC,QAAM,qBAAqB,SAAS;AACpC,QAAM,sBAAsB,qBAAqB;AACjD,QAAM,cAAc,MAAM,IAAI,CAAC,SAAU,OAAO,KAAK,cAAc,CAAE;AACrE,QAAM,kBAAkB,MAAM,IAAI,CAAC,SAAU,OAAO,KAAK,aAAa,CAAE;AACxE,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,gBAAgB,SAAS;AAC/B,QAAM,iBAAiB,gBAAgB,uBAAuB;AAC9D,QAAM,WACJ,kBAAkB,MAAM,SAAS,gBAAgB,aAAa,IAAI;AAEpE,SAAO,aAAa,QAAQ,kBAAkB,WAC1C,WACA;AACN;;;ACrDA,IAAM,eAAe,CAAC,MAAc,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC;AAElD,IAAM,eAAe,CAC1B,UACA,KACA,aACG;AACH,QAAM,QAAQ,SAAS;AACvB,QAAM,YAAY,YAAY,IAAI;AAElC,QAAM,OAAO,CAAC,gBAAwB;AACpC,UAAM,WAAW,cAAc,aAAa;AAC5C,UAAM,WAAW,KAAK,IAAI,aAAa,OAAO,GAAG,CAAC;AAClD,aAAS,aAAa,SAAS,MAAM,SAAS;AAC9C,QAAI,WAAW,EAAG,uBAAsB,IAAI;AAAA,EAC9C;AAEA,wBAAsB,IAAI;AAC5B;;;AClBO,SAAS,sBAAsB,OAAkC;AACtE,SAAO,MAAM,UAAU,CAAC,SAAS;AAC/B,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI,KAAK,QAAQ,iBAAiB,OAAQ,QAAO;AACjD,QAAI,KAAK,mBAAmB,aAAa,oBAAoB,MAAM;AACjE,aAAO;AACT,WAAO,MAAM,KAAK,KAAK,QAAQ,EAAE;AAAA,MAC/B,CAAC,OAAQ,GAAmB,QAAQ,iBAAiB;AAAA,IACvD;AAAA,EACF,CAAC;AACH;AAEO,SAAS,qBACd,UACA,gBACA,mBACA;AACA,MAAI,kBAAkB,QAAS;AAE/B,QAAM,QAAQ,sBAAsB,SAAS,OAAO;AAEpD,MAAI,UAAU,IAAI;AAChB,UAAM,eAAe,SAAS,QAAQ,KAAK,GAAG,cAAc;AAC5D,mBAAe,YAAY;AAC3B,sBAAkB,UAAU;AAAA,EAC9B;AACF;;;ANkEM,SAWqB,OAAAC,MAXrB;AAvEN,IAAM,iBAAsC,CAAC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,WAAW;AACb,MAAM;AACJ,QAAM,cAAc,OAAuB,IAAI;AAC/C,QAAM,WAAW,OAAkC,CAAC,CAAC;AACrD,QAAM,cAAc,OAAO,CAAC;AAC5B,QAAM,oBAAoB,OAAO,KAAK;AAEtC,QAAM,CAAC,cAAc,eAAe,IAAI,SAAwB;AAAA,IAC9D,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,oBAAoB;AAAA,EACtB,CAAC;AAED,QAAM,mBAAmB,YAAY,MAAM;AACzC,4BAAwB,YAAY,SAAU,eAAe;AAAA,EAC/D,GAAG,CAAC,CAAC;AAEL,QAAM,qBAAqB,YAAY,MAAM;AAC3C,0BAAsB,iBAAiB,IAAI;AAC3C;AAAA,MACE,MAAM,sBAAsB,iBAAiB,KAAK;AAAA,MAClD,WAAW;AAAA,IACb;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,iBAAiB;AAAA,IACrB,CAAC,WAAmB;AAClB,mBAAa,YAAY,SAAU,QAAQ,QAAQ;AACnD,yBAAmB;AAAA,IACrB;AAAA,IACA,CAAC,UAAU,kBAAkB;AAAA,EAC/B;AAEA,QAAM,uBAAuB,YAAY,MAAM;AAC7C,UAAM,SAAS,kBAAkB,YAAY,SAAU,SAAS,OAAO;AACvE,mBAAe,MAAM;AAAA,EACvB,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,mBAAmB,YAAY,MAAM;AACzC,UAAM,SAAS,cAAc,YAAY,SAAU,SAAS,OAAO;AACnE,mBAAe,MAAM;AAAA,EACvB,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,+BAA+B,YAAY,MAAM;AACrD,yBAAqB,UAAU,gBAAgB,iBAAiB;AAAA,EAClE,GAAG,CAAC,cAAc,CAAC;AAEnB,YAAU,MAAM;AACd,UAAM,eAAe;AACrB,WAAO,iBAAiB,UAAU,YAAY;AAC9C,qBAAiB;AACjB,WAAO,MAAM,OAAO,oBAAoB,UAAU,YAAY;AAAA,EAChE,GAAG,CAAC,gBAAgB,CAAC;AAErB,YAAU,MAAM;AACd,UAAM,qBAAqB,YAAY,SAAS,eAAe;AAC/D,QAAI,uBAAuB,YAAY,SAAS;AAC9C,kBAAY,UAAU;AACtB,uBAAiB;AACjB,mCAA6B;AAAA,IAC/B;AAAA,EACF,GAAG,CAAC,UAAU,kBAAkB,4BAA4B,CAAC;AAE7D,SACE,gBAAAA,KAAC,SAAI,WAAU,mCACb,+BAAC,SAAI,WAAU,yBACb;AAAA,oBAAAA,KAAC,SAAI,WAAU,wCACb,0BAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS;AAAA,QACT,WAAW;AAAA,gBACP,aAAa,qBAAqB,yCAAyC,EAAE;AAAA,gBAC7E,aAAa,oBAAoB,wCAAwC,EAAE;AAAA,QAC/E,OAAO,EAAE,YAAY,GAAG,QAAQ,KAAK;AAAA,QAEpC,wBAAc,gBAAAA,KAAC,kBAAa,MAAI,MAAC;AAAA;AAAA,IACpC,GACF;AAAA,IAEA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,WAAU;AAAA,QACV,UAAU;AAAA,QAEV,0BAAAA,KAAC,SAAI,WAAU,+BACZ,mBAAS,IAAI,UAAU,CAAC,OAAkB,MAAM;AAC/C,gBAAM,WAAW,OAAO,UAAU,YAAY,UAAU;AACxD,gBAAM,mBACJ,YACA,WAAW,SACX,MAAM,QAAQ,oBAAoB;AAEpC,iBACE,gBAAAA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK,CAAC,QAAS,SAAS,QAAQ,CAAC,IAAI;AAAA,cACrC,WAAU;AAAA,cACV,sBAAoB;AAAA,cACpB,OACE,OAAO,IAAI,SAAS,SAAS,IAAI,EAAE,cAAc,IAAI,IAAI,CAAC;AAAA,cAG3D;AAAA;AAAA,UACH;AAAA,QAEJ,CAAC,GACH;AAAA;AAAA,IACF;AAAA,IAEA,gBAAAA,KAAC,SAAI,WAAU,wCACb,0BAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS;AAAA,QACT,WAAW;AAAA,gBACP,aAAa,qBAAqB,yCAAyC,EAAE;AAAA,gBAC7E,aAAa,oBAAoB,wCAAwC,EAAE;AAAA,QAC/E,OAAO,EAAE,YAAY,GAAG,QAAQ,KAAK;AAAA,QAEpC,wBAAc,gBAAAA,KAAC,kBAAa;AAAA;AAAA,IAC/B,GACF;AAAA,KACF,GACF;AAEJ;AAEA,IAAO,iBAAQ;","names":["firstVisible","jsx"]}