From 429b0559201cfa53aff7eb4f9e68cdd8e260e88e Mon Sep 17 00:00:00 2001 From: Artur Pata Date: Wed, 7 May 2025 08:22:53 +0300 Subject: [PATCH] Update headlessui to v1.7.19, refactor site switcher (#5255) * Migrate some * Making progress * All fixed * Convert interval picker to tsx * Fix format * Fix tests * Make sure focus outline looks right on DropdownTabButton * Refactor Site Switcher to Popover * Fix site switcher test * Better jsdom mocks in assets tests * Try svg placeholder favicon * Update favicon test * Try giving transition config directly * Remove empty props * Remove unnecessary closeDropdown to prevent Firefox transition issue * Register open dropmenus globally This is needed to prevent invalid state when navigating with site hotkeys with Firefox while a dropdown is open and coming back using browser * Colocate popover-specific component * Clarify behaviour on hitting hotkey for current site * Try fix Firefox issue * Try 1.7.19 * Commit to @headlessui/react v1.7.x * Fix last two transition origins * Align active tab on baseline * Remove unneeded global dropmenu state * Add changelog * Funnels menu is searchable and scrollable * Fix transform origin * Stop funnels menu from holding onto search state * Mandate ref be passed to SearchInput from the outside --- CHANGELOG.md | 2 + assets/css/app.css | 13 - assets/jest.config.json | 1 + .../components/filter-operator-selector.js | 137 +- assets/js/dashboard/components/popover.tsx | 62 +- .../js/dashboard/components/search-input.tsx | 29 +- assets/js/dashboard/components/tabs.tsx | 214 ++ .../js/dashboard/filtering/segments.test.ts | 31 - assets/js/dashboard/filtering/segments.ts | 9 - .../dashboard/hooks/use-searchable-items.ts | 60 + assets/js/dashboard/keybinding.tsx | 29 - assets/js/dashboard/nav-menu/filter-menu.tsx | 8 +- .../dashboard/nav-menu/filters-bar.test.tsx | 25 +- assets/js/dashboard/nav-menu/filters-bar.tsx | 16 +- .../query-periods/comparison-period-menu.tsx | 7 +- .../query-periods/query-dates.test.tsx | 11 +- .../query-periods/query-period-menu.tsx | 7 +- .../query-periods/shared-menu-items.tsx | 6 +- .../segments/searchable-segments-section.tsx | 188 +- .../nav-menu/segments/segment-menu.tsx | 7 +- assets/js/dashboard/nav-menu/top-bar.test.tsx | 62 +- assets/js/dashboard/nav-menu/top-bar.tsx | 11 +- assets/js/dashboard/site-switcher.js | 283 -- assets/js/dashboard/site-switcher.tsx | 204 ++ assets/js/dashboard/stats/behaviours/index.js | 195 +- assets/js/dashboard/stats/devices/index.js | 42 +- .../dashboard/stats/graph/interval-picker.js | 173 -- .../dashboard/stats/graph/interval-picker.tsx | 190 ++ assets/js/dashboard/stats/locations/index.js | 44 +- .../stats/modals/breakdown-table.tsx | 63 +- assets/js/dashboard/stats/pages/index.js | 42 +- .../js/dashboard/stats/sources/source-list.js | 139 +- assets/package-lock.json | 2294 ++++------------- assets/package.json | 11 +- assets/test-utils/jsdom-mocks.ts | 5 + lib/plausible_web/plugs/favicon.ex | 7 +- priv/placeholder_favicon.svg | 1 + test/plausible_web/plugs/favicon_test.exs | 8 +- 38 files changed, 1686 insertions(+), 2950 deletions(-) create mode 100644 assets/js/dashboard/components/tabs.tsx create mode 100644 assets/js/dashboard/hooks/use-searchable-items.ts delete mode 100644 assets/js/dashboard/site-switcher.js create mode 100644 assets/js/dashboard/site-switcher.tsx delete mode 100644 assets/js/dashboard/stats/graph/interval-picker.js create mode 100644 assets/js/dashboard/stats/graph/interval-picker.tsx create mode 100644 assets/test-utils/jsdom-mocks.ts create mode 100644 priv/placeholder_favicon.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c3e316aab..291493e46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ All notable changes to this project will be documented in this file. - A session is now marked as a bounce if it has less than 2 pageviews and no interactive custom events. +- All dropmenus on dashboard are navigable with Tab (used to be a mix between tab and arrow keys), and no two dropmenus can be open at once on the dashboard + ### Fixed - Make clicking Compare / Disable Comparison in period picker menu close the menu diff --git a/assets/css/app.css b/assets/css/app.css index eb78a69a13..b23a085131 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -281,19 +281,6 @@ iframe[hidden] { z-index: 100; } -.active-prop-heading { - /* Properties related to text-decoration are all here in one place. TailwindCSS does support underline but that's about it. */ - text-decoration-line: underline; - text-decoration-color: #4338ca; /* tailwind's indigo-700 */ - text-decoration-thickness: 2px; -} - -@media (prefers-color-scheme: dark) { - .active-prop-heading { - text-decoration-color: #6366f1; /* tailwind's indigo-500 */ - } -} - /* This class is used for styling embedded dashboards. Do not remove. */ /* stylelint-disable */ /* prettier-ignore */ diff --git a/assets/jest.config.json b/assets/jest.config.json index 37c13e8d41..97f7025e31 100644 --- a/assets/jest.config.json +++ b/assets/jest.config.json @@ -8,6 +8,7 @@ }, "setupFilesAfterEnv": [ "/test-utils/extend-expect.ts", + "/test-utils/jsdom-mocks.ts", "/test-utils/reset-state.ts" ], "transform": { diff --git a/assets/js/dashboard/components/filter-operator-selector.js b/assets/js/dashboard/components/filter-operator-selector.js index c7afb46526..91c0853443 100644 --- a/assets/js/dashboard/components/filter-operator-selector.js +++ b/assets/js/dashboard/components/filter-operator-selector.js @@ -1,4 +1,4 @@ -import React, { Fragment, useRef } from 'react' +import React, { useRef } from 'react' import { FILTER_OPERATIONS, @@ -7,97 +7,88 @@ import { supportsIsNot, supportsHasDoneNot } from '../util/filters' -import { Menu, Transition } from '@headlessui/react' +import { Transition, Popover } from '@headlessui/react' import { ChevronDownIcon } from '@heroicons/react/20/solid' import classNames from 'classnames' -import { BlurMenuButtonOnEscape } from '../keybinding' +import { popover, BlurMenuButtonOnEscape } from './popover' export default function FilterOperatorSelector(props) { const filterName = props.forFilter const buttonRef = useRef() - function renderTypeItem(operation, shouldDisplay) { - return ( - shouldDisplay && ( - - {({ active }) => ( - props.onSelect(operation)} - className={classNames('cursor-pointer block px-4 py-2 text-sm', { - 'bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100': - active, - 'text-gray-700 dark:text-gray-200': !active - })} - > - {FILTER_OPERATIONS_DISPLAY_NAMES[operation]} - - )} - - ) - ) - } - - const containerClass = classNames('w-full', { - 'opacity-20 cursor-default pointer-events-none': props.isDisabled - }) - return ( -
- - {({ open }) => ( +
+ + {({ close: closeDropdown }) => ( <> -
- - {FILTER_OPERATIONS_DISPLAY_NAMES[props.selectedType]} - -
- - - + ] + ] + .filter(([_operation, supported]) => supported) + .map(([operation]) => ( + + ))} + )} -
+
) } diff --git a/assets/js/dashboard/components/popover.tsx b/assets/js/dashboard/components/popover.tsx index f2f3e7931e..82cb119924 100644 --- a/assets/js/dashboard/components/popover.tsx +++ b/assets/js/dashboard/components/popover.tsx @@ -1,5 +1,7 @@ -import { TransitionClasses } from '@headlessui/react' +import React, { RefObject } from 'react' import classNames from 'classnames' +import { isModifierPressed, isTyping, Keybind } from '../keybinding' +import { TransitionClasses } from '@headlessui/react' const TRANSITION_CONFIG: TransitionClasses = { enter: 'transition ease-out duration-100', @@ -11,8 +13,14 @@ const TRANSITION_CONFIG: TransitionClasses = { } const transition = { - props: TRANSITION_CONFIG, - classNames: { fullwidth: 'z-10 absolute left-0 right-0' } + props: { + ...TRANSITION_CONFIG + }, + classNames: { + fullwidth: 'z-10 absolute left-0 right-0 origin-top', + left: 'z-10 absolute left-0 origin-top-left', + right: 'z-10 absolute right-0 origin-top-right' + } } const panel = { @@ -29,7 +37,9 @@ const toggleButton = { 'bg-white dark:bg-gray-800 shadow text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-900', ghost: 'text-gray-700 dark:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-900', - truncatedText: 'truncate block font-medium' + truncatedText: 'truncate block font-medium', + linkLike: + 'text-gray-700 dark:text-gray-300 hover:text-indigo-600 dark:hover:text-indigo-600' } } @@ -37,7 +47,8 @@ const items = { classNames: { navigationLink: classNames( 'flex items-center justify-between', - 'px-4 py-2 text-sm leading-tight' + 'px-4 py-2 text-sm leading-tight', + 'cursor-pointer' ), selectedOption: classNames('data-[selected=true]:font-bold'), hoverLink: classNames( @@ -51,15 +62,9 @@ const items = { 'dark:focus-within:bg-gray-900', 'dark:focus-within:text-gray-100' ), - roundedStartEnd: classNames( - 'first-of-type:rounded-t-md', - 'last-of-type:rounded-b-md' - ), - roundedEnd: classNames('last-of-type:rounded-b-md'), - groupRoundedStartEnd: classNames( - 'group-first-of-type:rounded-t-md', - 'group-last-of-type:rounded-b-md' - ) + roundedStart: 'first-of-type:rounded-t-md', + roundedEnd: 'last-of-type:rounded-b-md', + groupRoundedEnd: 'group-last-of-type:rounded-b-md' } } @@ -69,3 +74,32 @@ export const popover = { transition, items } + +/** + * Rendering this component captures the Escape key on targetRef.current, a PopoverButton, + * blurring the element on Escape, and stopping the event from propagating. + * Needed to prevent other Escape handlers that may exist from running. + */ +export function BlurMenuButtonOnEscape({ + targetRef +}: { + targetRef: RefObject +}) { + return ( + { + const t = event.target as HTMLElement | null + if (typeof t?.blur === 'function') { + if (t === targetRef.current) { + t.blur() + event.stopPropagation() + } + } + }} + targetRef={targetRef} + shouldIgnoreWhen={[isModifierPressed, isTyping]} + /> + ) +} diff --git a/assets/js/dashboard/components/search-input.tsx b/assets/js/dashboard/components/search-input.tsx index 50a1e204d6..59c65c5c53 100644 --- a/assets/js/dashboard/components/search-input.tsx +++ b/assets/js/dashboard/components/search-input.tsx @@ -1,20 +1,26 @@ -import React, { ChangeEventHandler, useCallback, useState, useRef } from 'react' +import React, { + ChangeEventHandler, + useCallback, + useState, + RefObject +} from 'react' import { isModifierPressed, Keybind } from '../keybinding' import { useDebounce } from '../custom-hooks' import classNames from 'classnames' export const SearchInput = ({ + searchRef, onSearch, className, placeholderFocused = 'Search', placeholderUnfocused = 'Press / to search' }: { + searchRef: RefObject onSearch: (value: string) => void className?: string placeholderFocused?: string placeholderUnfocused?: string }) => { - const searchBoxRef = useRef(null) const [isFocused, setIsFocused] = useState(false) const onSearchInputChange: ChangeEventHandler = useCallback( @@ -26,13 +32,16 @@ export const SearchInput = ({ const debouncedOnSearchInputChange = useDebounce(onSearchInputChange) const blurSearchBox = useCallback(() => { - searchBoxRef.current?.blur() - }, []) + searchRef.current?.blur() + }, [searchRef]) - const focusSearchBox = useCallback((event: KeyboardEvent) => { - searchBoxRef.current?.focus() - event.stopPropagation() - }, []) + const focusSearchBox = useCallback( + (event: KeyboardEvent) => { + searchRef.current?.focus() + event.stopPropagation() + }, + [searchRef] + ) return ( <> @@ -41,7 +50,7 @@ export const SearchInput = ({ type="keyup" handler={blurSearchBox} shouldIgnoreWhen={[isModifierPressed, () => !isFocused]} - targetRef={searchBoxRef} + targetRef={searchRef} /> setIsFocused(false)} onFocus={() => setIsFocused(true)} - ref={searchBoxRef} + ref={searchRef} type="text" placeholder={isFocused ? placeholderFocused : placeholderUnfocused} className={classNames( diff --git a/assets/js/dashboard/components/tabs.tsx b/assets/js/dashboard/components/tabs.tsx new file mode 100644 index 0000000000..78eaaf75ad --- /dev/null +++ b/assets/js/dashboard/components/tabs.tsx @@ -0,0 +1,214 @@ +import { Popover, Transition } from '@headlessui/react' +import classNames from 'classnames' +import React, { ReactNode, useRef } from 'react' +import { ChevronDownIcon } from '@heroicons/react/20/solid' +import { popover, BlurMenuButtonOnEscape } from './popover' +import { useSearchableItems } from '../hooks/use-searchable-items' +import { SearchInput } from './search-input' +import { EllipsisHorizontalIcon } from '@heroicons/react/24/solid' + +export const TabWrapper = ({ + className, + children +}: { + className?: string + children: ReactNode +}) => ( +
+ {children} +
+) + +const TabButtonText = ({ + children, + active +}: { + children: ReactNode + active: boolean +}) => ( + + {children} + +) + +export const TabButton = ({ + className, + children, + onClick, + active +}: { + className?: string + children: ReactNode + onClick: () => void + active: boolean +}) => ( + +) + +export const DropdownTabButton = ({ + className, + transitionClassName, + active, + children, + ...optionsProps +}: { + className?: string + transitionClassName?: string + active: boolean + children: ReactNode +} & Omit) => { + const dropdownButtonRef = useRef(null) + + return ( + + {({ close: closeDropdown }) => ( + <> + + + {children} + + + + + + + + + + + )} + + ) +} + +type ItemsProps = { + closeDropdown: () => void + options: Array<{ selected: boolean; onClick: () => void; label: string }> + searchable?: boolean + collectionTitle?: string +} + +const Items = ({ + options, + searchable, + collectionTitle, + closeDropdown +}: ItemsProps) => { + const { + filteredData, + showableData, + showSearch, + searching, + searchRef, + handleSearchInput, + handleClearSearch, + handleShowAll, + countOfMoreToShow + } = useSearchableItems({ + data: options, + maxItemsInitially: searchable ? 5 : options.length, + itemMatchesSearchValue: (option, trimmedSearchString) => + option.label.toLowerCase().includes(trimmedSearchString.toLowerCase()) + }) + + const itemClassName = classNames( + 'w-full text-left', + popover.items.classNames.navigationLink, + popover.items.classNames.selectedOption, + popover.items.classNames.hoverLink, + { + [popover.items.classNames.roundedStart]: !searchable || !showSearch + // when the menu is not searchable, the first item needs rounded top + }, + popover.items.classNames.roundedEnd + ) + + return ( + <> + {searchable && showSearch && ( +
+ {collectionTitle && ( +
+ {collectionTitle} +
+ )} + +
+ )} +
+ {showableData.map(({ selected, label, onClick }, index) => { + return ( + + ) + })} + {countOfMoreToShow > 0 && ( + + )} + {searching && !filteredData.length && ( + + )} +
+ + ) +} diff --git a/assets/js/dashboard/filtering/segments.test.ts b/assets/js/dashboard/filtering/segments.test.ts index c8c3ba602a..6f5100f3c1 100644 --- a/assets/js/dashboard/filtering/segments.test.ts +++ b/assets/js/dashboard/filtering/segments.test.ts @@ -1,7 +1,6 @@ import { remapToApiFilters } from '../util/filters' import { formatSegmentIdAsLabelKey, - getFilterSegmentsByNameInsensitive, getSearchToApplySingleSegmentFilter, getSegmentNamePlaceholder, isSegmentIdLabelKey, @@ -17,36 +16,6 @@ import { Filter } from '../query' import { PlausibleSite } from '../site-context' import { Role, UserContextValue } from '../user-context' -describe(`${getFilterSegmentsByNameInsensitive.name}`, () => { - const unfilteredSegments = [ - { name: 'APAC Region' }, - { name: 'EMEA Region' }, - { name: 'Scandinavia' } - ] - it('generates insensitive filter function', () => { - expect( - unfilteredSegments.filter(getFilterSegmentsByNameInsensitive('region')) - ).toEqual([{ name: 'APAC Region' }, { name: 'EMEA Region' }]) - }) - - it('ignores preceding and following whitespace', () => { - expect( - unfilteredSegments.filter(getFilterSegmentsByNameInsensitive(' scandi ')) - ).toEqual([{ name: 'Scandinavia' }]) - }) - - it.each([[undefined], [''], [' '], ['\n\n']])( - 'generates always matching filter for search value %p', - (searchValue) => { - expect( - unfilteredSegments.filter( - getFilterSegmentsByNameInsensitive(searchValue) - ) - ).toEqual(unfilteredSegments) - } - ) -}) - describe(`${getSegmentNamePlaceholder.name}`, () => { it('gives readable result', () => { const placeholder = getSegmentNamePlaceholder({ diff --git a/assets/js/dashboard/filtering/segments.ts b/assets/js/dashboard/filtering/segments.ts index 791fc8e2a0..e3e6aa0cb8 100644 --- a/assets/js/dashboard/filtering/segments.ts +++ b/assets/js/dashboard/filtering/segments.ts @@ -66,15 +66,6 @@ export function handleSegmentResponse( } } -export function getFilterSegmentsByNameInsensitive( - search?: string -): (s: Pick) => boolean { - return (s) => - search?.trim().length - ? s.name.toLowerCase().includes(search.trim().toLowerCase()) - : true -} - export const getSegmentNamePlaceholder = ( query: Pick ) => diff --git a/assets/js/dashboard/hooks/use-searchable-items.ts b/assets/js/dashboard/hooks/use-searchable-items.ts new file mode 100644 index 0000000000..992633936d --- /dev/null +++ b/assets/js/dashboard/hooks/use-searchable-items.ts @@ -0,0 +1,60 @@ +import { useCallback, useEffect, useRef, useState } from 'react' + +export function useSearchableItems({ + data, + maxItemsInitially, + itemMatchesSearchValue +}: { + data: TItem[] + maxItemsInitially: number + itemMatchesSearchValue: (t: TItem, trimmedSearchString: string) => boolean +}): { + data: TItem[] + filteredData: TItem[] + showableData: TItem[] + searchRef: React.RefObject + showSearch: boolean + searching: boolean + countOfMoreToShow: number + handleSearchInput: (v: string) => void + handleClearSearch: () => void + handleShowAll: () => void +} { + const searchRef = useRef(null) + const [searchValue, setSearch] = useState() + const [showAll, setShowAll] = useState(false) + const trimmedSearch = searchValue?.trim() + const searching = !!trimmedSearch?.length + + useEffect(() => { + setShowAll(false) + }, [searching]) + + const filteredData = searching + ? data.filter((item) => itemMatchesSearchValue(item, trimmedSearch)) + : data + + const showableData = showAll + ? filteredData + : filteredData.slice(0, maxItemsInitially) + + const handleClearSearch = useCallback(() => { + if (searchRef.current) { + searchRef.current.value = '' + setSearch(undefined) + } + }, []) + + return { + searchRef, + data, + filteredData, + showableData, + showSearch: data.length > maxItemsInitially, + searching, + countOfMoreToShow: filteredData.length - showableData.length, + handleSearchInput: setSearch, + handleClearSearch, + handleShowAll: () => setShowAll(true) + } +} diff --git a/assets/js/dashboard/keybinding.tsx b/assets/js/dashboard/keybinding.tsx index 1a56e8f78a..636b6c952b 100644 --- a/assets/js/dashboard/keybinding.tsx +++ b/assets/js/dashboard/keybinding.tsx @@ -153,32 +153,3 @@ export function KeybindHint({ ) } - -/** - * Rendering this component captures the Escape key on targetRef.current, - * blurring the element on Escape, and stopping the event from propagating. - * Needed to prevent other Escape handlers that may exist from running. - */ -export function BlurMenuButtonOnEscape({ - targetRef: targetRef -}: { - targetRef: RefObject -}) { - return ( - { - const t = event.target as HTMLElement | null - if (typeof t?.blur === 'function') { - if (t === targetRef.current) { - t.blur() - event.stopPropagation() - } - } - }} - targetRef={targetRef} - shouldIgnoreWhen={[isModifierPressed, isTyping]} - /> - ) -} diff --git a/assets/js/dashboard/nav-menu/filter-menu.tsx b/assets/js/dashboard/nav-menu/filter-menu.tsx index 50e9ee36b7..1120e7e074 100644 --- a/assets/js/dashboard/nav-menu/filter-menu.tsx +++ b/assets/js/dashboard/nav-menu/filter-menu.tsx @@ -7,10 +7,9 @@ import { PlausibleSite, useSiteContext } from '../site-context' import { filterRoute } from '../router' import { MagnifyingGlassIcon } from '@heroicons/react/24/outline' import { Popover, Transition } from '@headlessui/react' -import { popover } from '../components/popover' +import { popover, BlurMenuButtonOnEscape } from '../components/popover' import classNames from 'classnames' import { AppNavigationLink } from '../navigation/use-app-navigate' -import { BlurMenuButtonOnEscape } from '../keybinding' import { SearchableSegmentsSection } from './segments/searchable-segments-section' export function getFilterListItems({ @@ -67,15 +66,16 @@ const FilterMenuItems = ({ closeDropdown }: { closeDropdown: () => void }) => {
{columns.map((filterGroups, index) => ( diff --git a/assets/js/dashboard/nav-menu/filters-bar.test.tsx b/assets/js/dashboard/nav-menu/filters-bar.test.tsx index 42c1e07c2f..ea228a61bf 100644 --- a/assets/js/dashboard/nav-menu/filters-bar.test.tsx +++ b/assets/js/dashboard/nav-menu/filters-bar.test.tsx @@ -5,24 +5,14 @@ import { TestContextProviders } from '../../../test-utils/app-context-providers' import { FiltersBar, handleVisibility } from './filters-bar' import { getRouterBasepath } from '../router' import { stringifySearch } from '../util/url-search-params' +import { mockAnimationsApi, mockResizeObserver } from 'jsdom-testing-mocks' + +mockAnimationsApi() +const resizeObserver = mockResizeObserver() const domain = 'dummy.site' -beforeAll(() => { - const mockResizeObserver = jest.fn( - (handleEntries) => - ({ - observe: jest.fn().mockImplementation((entry) => { - handleEntries([entry], null as unknown as ResizeObserver) - }), - unobserve: jest.fn(), - disconnect: jest.fn() - }) as unknown as ResizeObserver - ) - global.ResizeObserver = mockResizeObserver -}) - -test('user can see expected filters and clear them one by one or all together', async () => { +test('user can see expected filters and clear them one by one or all together on small screens', async () => { const searchRecord = { filters: [ ['is', 'country', ['DE']], @@ -67,10 +57,13 @@ test('user can see expected filters and clear them one by one or all together', } ) + // needed to initiate the layout calculation effect of the component + resizeObserver.resize() + const queryFilterPills = () => screen.queryAllByRole('link', { hidden: false, name: /.* is .*/i }) - // all filters appear in See more menu + // all filters appear in See more menu (see the mock widths in props) expect(queryFilterPills().map((m) => m.textContent)).toEqual([]) await userEvent.click( diff --git a/assets/js/dashboard/nav-menu/filters-bar.tsx b/assets/js/dashboard/nav-menu/filters-bar.tsx index 6988d93861..5bb9150825 100644 --- a/assets/js/dashboard/nav-menu/filters-bar.tsx +++ b/assets/js/dashboard/nav-menu/filters-bar.tsx @@ -5,8 +5,7 @@ import { AppliedFilterPillsList, PILL_X_GAP_PX } from './filter-pills-list' import { useQueryContext } from '../query-context' import { AppNavigationLink } from '../navigation/use-app-navigate' import { Popover, Transition } from '@headlessui/react' -import { popover } from '../components/popover' -import { BlurMenuButtonOnEscape } from '../keybinding' +import { popover, BlurMenuButtonOnEscape } from '../components/popover' import { isSegmentFilter } from '../filtering/segments' import { useRoutelessModalsContext } from '../navigation/routeless-modals-context' import { DashboardQuery } from '../query' @@ -273,11 +272,11 @@ const SeeMoreMenu = ({ )} - {actions.map((action, index) => { + {actions.map((action) => { const linkClassName = classNames( popover.items.classNames.navigationLink, popover.items.classNames.selectedOption, popover.items.classNames.hoverLink, - index === 0 && !showMoreFilters - ? popover.items.classNames.roundedStartEnd - : popover.items.classNames.roundedEnd, + { + [popover.items.classNames.roundedStart]: !showMoreFilters // rounded start is needed when there's no filters panel above + }, + popover.items.classNames.roundedEnd, 'whitespace-nowrap' ) diff --git a/assets/js/dashboard/nav-menu/query-periods/comparison-period-menu.tsx b/assets/js/dashboard/nav-menu/query-periods/comparison-period-menu.tsx index 5ca9d47492..942062ebfe 100644 --- a/assets/js/dashboard/nav-menu/query-periods/comparison-period-menu.tsx +++ b/assets/js/dashboard/nav-menu/query-periods/comparison-period-menu.tsx @@ -3,7 +3,6 @@ import { clearedComparisonSearch } from '../../query' import classNames from 'classnames' import { useQueryContext } from '../../query-context' import { useSiteContext } from '../../site-context' -import { BlurMenuButtonOnEscape } from '../../keybinding' import { AppNavigationLink, useAppNavigate @@ -18,7 +17,7 @@ import { getSearchToApplyCustomComparisonDates } from '../../query-time-periods' import { Popover, Transition } from '@headlessui/react' -import { popover } from '../../components/popover' +import { popover, BlurMenuButtonOnEscape } from '../../components/popover' import { datemenuButtonClassName, DateMenuChevron, @@ -46,11 +45,11 @@ export const ComparisonPeriodMenuItems = ({ return ( diff --git a/assets/js/dashboard/nav-menu/query-periods/query-dates.test.tsx b/assets/js/dashboard/nav-menu/query-periods/query-dates.test.tsx index c6a543d6ab..7437dc204a 100644 --- a/assets/js/dashboard/nav-menu/query-periods/query-dates.test.tsx +++ b/assets/js/dashboard/nav-menu/query-periods/query-dates.test.tsx @@ -6,6 +6,10 @@ import { stringifySearch } from '../../util/url-search-params' import { useNavigate } from 'react-router-dom' import { getRouterBasepath } from '../../router' import { QueryPeriodsPicker } from './query-periods-picker' +import { mockAnimationsApi, mockResizeObserver } from 'jsdom-testing-mocks' + +mockAnimationsApi() +mockResizeObserver() const domain = 'picking-query-dates.test' const periodStorageKey = `period__${domain}` @@ -47,10 +51,11 @@ test('user can select a new period and its value is stored', async () => { ) }) + expect(screen.queryByTestId('datemenu')).toBeNull() await userEvent.click(screen.getByText('Last 28 days')) expect(screen.getByTestId('datemenu')).toBeVisible() await userEvent.click(screen.getByText('All time')) - expect(screen.queryByTestId('datemenu')).toBeNull() + expect(screen.queryByTestId('datemenu')).not.toBeInTheDocument() expect(localStorage.getItem(periodStorageKey)).toBe('all') }) @@ -165,10 +170,14 @@ test('going back resets the stored query period to previous value', async () => await userEvent.click(screen.getByText('Last 28 days')) await userEvent.click(screen.getByText('Year to Date')) + expect(screen.queryByTestId('datemenu')).not.toBeInTheDocument() + expect(localStorage.getItem(periodStorageKey)).toBe('year') await userEvent.click(screen.getByText('Year to Date')) await userEvent.click(screen.getByText('Month to Date')) + expect(screen.queryByTestId('datemenu')).not.toBeInTheDocument() + expect(localStorage.getItem(periodStorageKey)).toBe('month') await userEvent.click(screen.getByTestId('browser-back')) diff --git a/assets/js/dashboard/nav-menu/query-periods/query-period-menu.tsx b/assets/js/dashboard/nav-menu/query-periods/query-period-menu.tsx index 9620bcb508..6674479c48 100644 --- a/assets/js/dashboard/nav-menu/query-periods/query-period-menu.tsx +++ b/assets/js/dashboard/nav-menu/query-periods/query-period-menu.tsx @@ -3,7 +3,6 @@ import classNames from 'classnames' import { useQueryContext } from '../../query-context' import { useSiteContext } from '../../site-context' import { - BlurMenuButtonOnEscape, isModifierPressed, isTyping, Keybind, @@ -25,7 +24,7 @@ import { import { useMatch } from 'react-router-dom' import { rootRoute } from '../../router' import { Popover, Transition } from '@headlessui/react' -import { popover } from '../../components/popover' +import { popover, BlurMenuButtonOnEscape } from '../../components/popover' import { datemenuButtonClassName, DateMenuChevron, @@ -152,11 +151,11 @@ const QueryPeriodMenuInner = ({ <> (({ children, className }, ref) => { return ( diff --git a/assets/js/dashboard/nav-menu/segments/searchable-segments-section.tsx b/assets/js/dashboard/nav-menu/segments/searchable-segments-section.tsx index 8985370288..7ede78def5 100644 --- a/assets/js/dashboard/nav-menu/segments/searchable-segments-section.tsx +++ b/assets/js/dashboard/nav-menu/segments/searchable-segments-section.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useState } from 'react' +import React from 'react' import { useQueryContext } from '../../query-context' import { useSiteContext } from '../../site-context' import { formatSegmentIdAsLabelKey, - getFilterSegmentsByNameInsensitive, isSegmentFilter, SavedSegmentPublic, SavedSegment, @@ -21,15 +20,16 @@ import { AppNavigationLink } from '../../navigation/use-app-navigate' import { MenuSeparator } from '../nav-menu-components' import { Role, useUserContext } from '../../user-context' import { useSegmentsContext } from '../../filtering/segments-context' +import { useSearchableItems } from '../../hooks/use-searchable-items' const linkClassName = classNames( popover.items.classNames.navigationLink, popover.items.classNames.selectedOption, popover.items.classNames.hoverLink, - popover.items.classNames.groupRoundedStartEnd + popover.items.classNames.groupRoundedEnd ) -const initialSliceLength = 5 +const INITIAL_SEGMENTS_SHOWN = 5 export const SearchableSegmentsSection = ({ closeList @@ -44,105 +44,109 @@ export const SearchableSegmentsSection = ({ const isPublicListQuery = !user.loggedIn || user.role === Role.public - const data = segmentsContext.segments.filter((segment) => - isListableSegment({ segment, site, user }) - ) - - const [searchValue, setSearch] = useState() - const [showAll, setShowAll] = useState(false) - - const searching = !searchValue?.trim().length - - useEffect(() => { - setShowAll(false) - }, [searching]) - - const filteredData = data?.filter( - getFilterSegmentsByNameInsensitive(searchValue) - ) - - const showableSlice = showAll - ? filteredData - : filteredData?.slice(0, initialSliceLength) + const { + data, + filteredData, + showableData, + showSearch, + countOfMoreToShow, + handleShowAll, + handleClearSearch, + handleSearchInput, + searchRef, + searching + } = useSearchableItems({ + data: segmentsContext.segments.filter((segment) => + isListableSegment({ segment, site, user }) + ), + maxItemsInitially: INITIAL_SEGMENTS_SHOWN, + itemMatchesSearchValue: (segment, trimmedSearch) => + segment.name.toLowerCase().includes(trimmedSearch.toLowerCase()) + }) if (expandedSegment) { return null } + if (!data.length) { + return null + } + return ( <> - {!!data?.length && ( - <> - -
-
- Segments -
- {data.length > initialSliceLength && ( - - )} -
+ +
+
+ Segments +
+ {showSearch && ( + + )} +
- {showableSlice!.map((segment) => { - return ( - -
{segment.name}
-
- {SEGMENT_TYPE_LABELS[segment.type]} -
- - +
+ {showableData.map((segment) => { + return ( + +
{segment.name}
+
+ {SEGMENT_TYPE_LABELS[segment.type]}
- } - > - -
- ) - })} - {!!filteredData?.length && - !!showableSlice?.length && - filteredData?.length > showableSlice?.length && - showAll === false && ( - - s} - onClick={() => setShowAll(true)} - > - {`Show ${filteredData.length - showableSlice.length} more`} - - - - )} - - )} - {!!data?.length && searchValue && !showableSlice?.length && ( + + +
+ } + > + +
+ ) + })} + {countOfMoreToShow > 0 && ( + + + + )} +
+ {searching && !filteredData.length && ( -
+
+
)} diff --git a/assets/js/dashboard/nav-menu/segments/segment-menu.tsx b/assets/js/dashboard/nav-menu/segments/segment-menu.tsx index b52d9eb515..3165d1fdcb 100644 --- a/assets/js/dashboard/nav-menu/segments/segment-menu.tsx +++ b/assets/js/dashboard/nav-menu/segments/segment-menu.tsx @@ -21,7 +21,8 @@ const linkClassName = classNames( popover.items.classNames.navigationLink, popover.items.classNames.selectedOption, popover.items.classNames.hoverLink, - popover.items.classNames.roundedStartEnd + popover.items.classNames.roundedStart, + popover.items.classNames.roundedEnd ) const buttonClassName = classNames( 'text-white font-medium bg-indigo-600 hover:bg-indigo-700' @@ -92,11 +93,11 @@ export const SegmentMenu = () => { /> diff --git a/assets/js/dashboard/nav-menu/top-bar.test.tsx b/assets/js/dashboard/nav-menu/top-bar.test.tsx index 735f9feb2e..b091c6f535 100644 --- a/assets/js/dashboard/nav-menu/top-bar.test.tsx +++ b/assets/js/dashboard/nav-menu/top-bar.test.tsx @@ -10,30 +10,21 @@ import userEvent from '@testing-library/user-event' import { TestContextProviders } from '../../../test-utils/app-context-providers' import { TopBar } from './top-bar' import { MockAPI } from '../../../test-utils/mock-api' +import { + mockAnimationsApi, + mockResizeObserver, + mockIntersectionObserver +} from 'jsdom-testing-mocks' + +mockAnimationsApi() +mockResizeObserver() +mockIntersectionObserver() const domain = 'dummy.site' -const domains = [domain, 'example.com', 'blog.example.com'] let mockAPI: MockAPI beforeAll(() => { - global.IntersectionObserver = jest.fn( - () => - ({ - observe: jest.fn(), - unobserve: jest.fn(), - disconnect: jest.fn() - }) as unknown as IntersectionObserver - ) - global.ResizeObserver = jest.fn( - () => - ({ - observe: jest.fn(), - unobserve: jest.fn(), - disconnect: jest.fn() - }) as unknown as ResizeObserver - ) - mockAPI = new MockAPI().start() }) @@ -43,10 +34,18 @@ afterAll(() => { beforeEach(() => { mockAPI.clear() - mockAPI.get('/api/sites', { data: domains.map((domain) => ({ domain })) }) + mockAPI.get('/api/sites', { data: [{ domain }] }) }) test('user can open and close site switcher', async () => { + mockAPI.get('/api/sites', { + data: [domain, 'example.com', 'blog.example.com', 'aççented.ca'].map( + (domain) => ({ + domain + }) + ) + }) + render(, { wrapper: (props) => ( @@ -55,16 +54,24 @@ test('user can open and close site switcher', async () => { const toggleSiteSwitcher = screen.getByRole('button', { name: domain }) await userEvent.click(toggleSiteSwitcher) - expect(screen.queryAllByRole('link').map((el) => el.textContent)).toEqual( + expect( + screen + .queryAllByRole('link') + .map((el) => ({ text: el.textContent, href: el.getAttribute('href') })) + ).toEqual( [ - ['example.com', '2'], - ['blog.example.com', '3'] - ].map((a) => a.join('')) - ) - expect(screen.queryAllByRole('menuitem').map((el) => el.textContent)).toEqual( - ['Site Settings', 'View All'] + { text: ['Site settings'], href: `/${domain}/settings/general` }, + { text: ['dummy.site', '1'], href: '#' }, + { text: ['example.com', '2'], href: `/example.com` }, + { text: ['blog.example.com', '3'], href: `/blog.example.com` }, + { text: ['aççented.ca', '4'], href: `/a%C3%A7%C3%A7ented.ca` }, + { text: ['View all'], href: '/sites' } + ].map((l) => ({ ...l, text: l.text.join('') })) ) + + expect(screen.queryByTestId('sitemenu')).toBeInTheDocument() await userEvent.click(toggleSiteSwitcher) + expect(screen.queryByTestId('sitemenu')).not.toBeInTheDocument() expect(screen.queryAllByRole('menuitem')).toEqual([]) }) @@ -89,7 +96,8 @@ test('user can open and close filters dropdown', async () => { 'Goal' ]) await userEvent.click(toggleFilters) - expect(screen.queryAllByRole('menuitem')).toEqual([]) + expect(screen.queryByTestId('filtermenu')).not.toBeInTheDocument() + expect(screen.queryAllByRole('link')).toEqual([]) }) test('current visitors renders when visitors are present and disappears after visitors are null', async () => { diff --git a/assets/js/dashboard/nav-menu/top-bar.tsx b/assets/js/dashboard/nav-menu/top-bar.tsx index 5aef3e0deb..58c95dac8e 100644 --- a/assets/js/dashboard/nav-menu/top-bar.tsx +++ b/assets/js/dashboard/nav-menu/top-bar.tsx @@ -1,7 +1,6 @@ import React, { ReactNode, useRef } from 'react' -import SiteSwitcher from '../site-switcher' +import { SiteSwitcher } from '../site-switcher' import { useSiteContext } from '../site-context' -import { useUserContext } from '../user-context' import CurrentVisitors from '../stats/current-visitors' import classNames from 'classnames' import { useInView } from 'react-intersection-observer' @@ -44,18 +43,12 @@ function TopBarStickyWrapper({ children }: { children: ReactNode }) { } function TopBarInner({ showCurrentVisitors }: TopBarProps) { - const site = useSiteContext() - const user = useUserContext() const leftActionsRef = useRef(null) return (
- + {showCurrentVisitors && ( )} diff --git a/assets/js/dashboard/site-switcher.js b/assets/js/dashboard/site-switcher.js deleted file mode 100644 index 2612bc23d7..0000000000 --- a/assets/js/dashboard/site-switcher.js +++ /dev/null @@ -1,283 +0,0 @@ -/** - * @prettier - */ -import React from 'react' -import { Transition } from '@headlessui/react' -import { Cog8ToothIcon, ChevronDownIcon } from '@heroicons/react/20/solid' -import classNames from 'classnames' - -function Favicon({ domain, className }) { - return ( - { - e.target.onerror = null - e.target.src = '/favicon/sources/placeholder' - }} - referrerPolicy="no-referrer" - className={className} - /> - ) -} - -export default class SiteSwitcher extends React.Component { - constructor() { - super() - this.handleClick = this.handleClick.bind(this) - this.handleKeydown = this.handleKeydown.bind(this) - this.populateSites = this.populateSites.bind(this) - this.toggle = this.toggle.bind(this) - this.siteSwitcherButton = React.createRef() - this.state = { - open: false, - sites: null, - error: null, - loading: true - } - } - - componentDidMount() { - this.populateSites() - this.siteSwitcherButton.current.addEventListener('click', this.toggle) - document.addEventListener('keydown', this.handleKeydown) - document.addEventListener('click', this.handleClick, false) - } - - componentWillUnmount() { - this.siteSwitcherButton.current.removeEventListener('click', this.toggle) - document.removeEventListener('keydown', this.handleKeydown) - document.removeEventListener('click', this.handleClick, false) - } - - populateSites() { - if (!this.props.loggedIn) return - - fetch('/api/sites') - .then((response) => { - if (!response.ok) { - throw response - } - return response.json() - }) - .then((sites) => - this.setState({ - loading: false, - sites: sites.data.map((s) => s.domain) - }) - ) - .catch((e) => this.setState({ loading: false, error: e })) - } - - handleClick(e) { - // If this is an interaction with the dropdown menu itself, do nothing. - if (this.dropDownNode && this.dropDownNode.contains(e.target)) return - - // If the dropdown is not open, do nothing. - if (!this.state.open) return - - // In any other case, close it. - this.setState({ open: false }) - } - - handleKeydown(e) { - if (!this.props.loggedIn) return - - const { site } = this.props - const { sites } = this.state - - if (e.target.tagName === 'INPUT') return true - if ( - e.ctrlKey || - e.metaKey || - e.altKey || - e.isComposing || - e.keyCode === 229 || - !sites - ) - return - - const siteNum = parseInt(e.key) - - if ( - 1 <= siteNum && - siteNum <= 9 && - siteNum <= sites.length && - sites[siteNum - 1] !== site.domain - ) { - // has to change window.location because Router is rendered with /${site.domain} as the basepath - window.location = `/${encodeURIComponent(sites[siteNum - 1])}` - } - } - - toggle(e) { - /** - * React doesn't seem to prioritise its own events when events are bubbling, and is unable to stop its events from propagating to the document's (root) event listeners which are attached on the DOM. - * - * A simple trick is to hook up our own click event listener via a ref node, which allows React to manage events in this situation better between the two. - */ - e.stopPropagation() - e.preventDefault() - if (!this.props.loggedIn) return - - this.setState((prevState) => ({ - open: !prevState.open - })) - - if (this.props.loggedIn && !this.state.sites) { - this.populateSites() - } - } - - renderSiteLink(domain, index) { - const extraClass = - domain === this.props.site.domain - ? 'font-medium text-gray-900 dark:text-gray-100 cursor-default font-bold' - : 'hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-900 dark:focus:text-gray-100' - const showHotkey = this.props.loggedIn && this.state.sites.length > 1 - return ( - - - - - {domain} - - - {showHotkey && index < 9 ? {index + 1} : null} - - ) - } - - renderSettingsLink() { - if ( - ['owner', 'admin', 'editor', 'super_admin'].includes( - this.props.currentUserRole - ) - ) { - return ( - - -
-
- ) - } - } - - /** - * Render a dropdown regardless of whether the user is logged in or not. In case they are not logged in (such as in an embed), the dropdown merely contains the current domain name. - */ - renderDropdown() { - if (this.state.loading) { - return ( -
-
-
-
-
- ) - } else if (this.state.error) { - return ( -
- Something went wrong, try again -
- ) - } else if (!this.props.loggedIn) { - return ( - -
- {[this.props.site.domain].map(this.renderSiteLink.bind(this))} -
-
- ) - } else { - return ( - - {this.renderSettingsLink()} -
- {this.state.sites.map(this.renderSiteLink.bind(this))} -
-
- - View All - -
- ) - } - } - - render() { - const hoverClass = this.props.loggedIn - ? 'hover:text-gray-500 dark:hover:text-gray-200 focus:border-blue-300 focus:ring ' - : 'cursor-default' - - return ( -
- - - -
(this.dropDownNode = node)} - > -
- {this.renderDropdown()} -
-
-
-
- ) - } -} diff --git a/assets/js/dashboard/site-switcher.tsx b/assets/js/dashboard/site-switcher.tsx new file mode 100644 index 0000000000..d42a475610 --- /dev/null +++ b/assets/js/dashboard/site-switcher.tsx @@ -0,0 +1,204 @@ +/** + * @prettier + */ +import React, { useRef } from 'react' +import { Popover, Transition } from '@headlessui/react' +import { ChevronDownIcon } from '@heroicons/react/20/solid' +import { Cog8ToothIcon } from '@heroicons/react/24/outline' +import classNames from 'classnames' +import { isModifierPressed, isTyping, Keybind, KeybindHint } from './keybinding' +import { popover, BlurMenuButtonOnEscape } from './components/popover' +import { useQuery } from '@tanstack/react-query' +import { Role, useUserContext } from './user-context' +import { PlausibleSite, useSiteContext } from './site-context' +import { MenuSeparator } from './nav-menu/nav-menu-components' +import { useMatch } from 'react-router-dom' +import { rootRoute } from './router' +import { get } from './api' +import { ErrorPanel } from './components/error-panel' +import { useRoutelessModalsContext } from './navigation/routeless-modals-context' + +const Favicon = ({ + domain, + className +}: { + domain: string + className?: string +}) => ( + { + const target = e.target as HTMLImageElement + target.onerror = null + target.src = '/favicon/sources/placeholder' + }} + referrerPolicy="no-referrer" + className={className} + /> +) + +const menuItemClassName = classNames( + popover.items.classNames.navigationLink, + popover.items.classNames.selectedOption, + popover.items.classNames.hoverLink, + popover.items.classNames.roundedStart, + popover.items.classNames.roundedEnd +) + +const getSwitchToSiteURL = ( + currentSite: PlausibleSite, + site: { domain: string } +): string | null => { + // Prevents reloading the page when the current site is selected + if (currentSite.domain === site.domain) { + return null + } + return `/${encodeURIComponent(site.domain)}` +} + +export const SiteSwitcher = () => { + const dashboardRouteMatch = useMatch(rootRoute.path) + const { modal } = useRoutelessModalsContext() + const user = useUserContext() + const currentSite = useSiteContext() + const buttonRef = useRef(null) + const sitesQuery = useQuery({ + enabled: user.loggedIn, + queryKey: ['sites'], + queryFn: async (): Promise<{ data: Array<{ domain: string }> }> => { + const response = await get('/api/sites') + return response + }, + placeholderData: (previousData) => previousData + }) + + const sitesInDropdown = user.loggedIn + ? sitesQuery.data?.data + : // show only current site in dropdown when viewing public / embedded dashboard + [{ domain: currentSite.domain }] + + const canSeeSiteSettings: boolean = + user.loggedIn && + [Role.owner, Role.admin, Role.editor, 'super_admin'].includes(user.role) + + const canSeeViewAllSites: boolean = user.loggedIn + + return ( + + {({ close: closePopover }) => ( + <> + {!!dashboardRouteMatch && + !modal && + sitesQuery.data?.data.slice(0, 8).map(({ domain }, index) => ( + { + const url = getSwitchToSiteURL(currentSite, { domain }) + if (!url) { + closePopover() + } else { + closePopover() + window.location.assign(url) + } + }} + shouldIgnoreWhen={[isModifierPressed, isTyping]} + targetRef="document" + /> + ))} + + + + + + + + + + {canSeeSiteSettings && ( + <> + + + Site settings + + + + )} + {sitesQuery.isLoading && ( +
+
+
+
+
+ )} + {sitesQuery.isError && ( +
+ +
+ )} + {!!sitesInDropdown && + sitesInDropdown.map(({ domain }, index) => ( + closePopover() + : () => {} + } + > + + {domain} + {sitesInDropdown.length > 1 && ( + {index + 1} + )} + + ))} + {canSeeViewAllSites && ( + <> + + + View all + + + )} + + + + )} + + ) +} diff --git a/assets/js/dashboard/stats/behaviours/index.js b/assets/js/dashboard/stats/behaviours/index.js index 43d8b8c74d..cce4bed8af 100644 --- a/assets/js/dashboard/stats/behaviours/index.js +++ b/assets/js/dashboard/stats/behaviours/index.js @@ -1,13 +1,4 @@ -import React, { - Fragment, - useState, - useEffect, - useCallback, - useRef -} from 'react' -import { Menu, Transition } from '@headlessui/react' -import { ChevronDownIcon } from '@heroicons/react/20/solid' -import classNames from 'classnames' +import React, { useState, useEffect, useCallback } from 'react' import * as storage from '../../util/storage' import ImportedQueryUnsupportedWarning from '../imported-query-unsupported-warning' import GoalConversions, { @@ -20,7 +11,7 @@ import { hasConversionGoalFilter } from '../../util/filters' import { useSiteContext } from '../../site-context' import { useQueryContext } from '../../query-context' import { useUserContext } from '../../user-context' -import { BlurMenuButtonOnEscape } from '../../keybinding' +import { DropdownTabButton, TabButton, TabWrapper } from '../../components/tabs' /*global BUILD_EXTRA*/ /*global require*/ @@ -35,10 +26,6 @@ function maybeRequire() { const Funnel = maybeRequire().default -const ACTIVE_CLASS = - 'inline-block h-5 text-indigo-700 dark:text-indigo-500 font-bold active-prop-heading truncate text-left' -const DEFAULT_CLASS = 'hover:text-indigo-600 cursor-pointer truncate text-left' - export const CONVERSIONS = 'conversions' export const PROPS = 'props' export const FUNNELS = 'funnels' @@ -53,7 +40,6 @@ export default function Behaviours({ importedDataInView }) { const { query } = useQueryContext() const site = useSiteContext() const user = useUserContext() - const buttonRef = useRef() const adminAccess = ['owner', 'admin', 'editor', 'super_admin'].includes( user.role ) @@ -66,9 +52,6 @@ export default function Behaviours({ importedDataInView }) { const [mode, setMode] = useState(defaultMode()) const [loading, setLoading] = useState(true) - const [funnelNames, _setFunnelNames] = useState( - site.funnels.map(({ name }) => name) - ) const [selectedFunnel, setSelectedFunnel] = useState(defaultSelectedFunnel()) const [showingPropsForGoalFilter, setShowingPropsForGoalFilter] = @@ -117,12 +100,12 @@ export default function Behaviours({ importedDataInView }) { ) } - function setFunnel(selectedFunnel) { + function setFunnelFactory(selectedFunnelName) { return () => { storage.setItem(tabKey, FUNNELS) - storage.setItem(funnelKey, selectedFunnel) + storage.setItem(funnelKey, selectedFunnelName) setMode(FUNNELS) - setSelectedFunnel(selectedFunnel) + setSelectedFunnel(selectedFunnelName) } } @@ -140,96 +123,11 @@ export default function Behaviours({ importedDataInView }) { } } - function hasFunnels() { - return site.funnels.length > 0 && site.funnelsAvailable - } - - function tabFunnelPicker() { - return ( - - -
- - - Funnels - - -
- - - -
- {funnelNames.map((funnelName) => { - return ( - - {({ active }) => ( - - {funnelName} - - )} - - ) - })} -
-
-
-
- ) - } - - function tabSwitcher(toMode, displayName) { - const className = classNames({ - [ACTIVE_CLASS]: mode == toMode, - [DEFAULT_CLASS]: mode !== toMode - }) - const setTab = () => { - storage.setItem(tabKey, toMode) - setMode(toMode) + function setTabFactory(tab) { + return () => { + storage.setItem(tabKey, tab) + setMode(tab) } - - return ( -
- {displayName} -
- ) - } - - function tabs() { - return ( -
- {isEnabled(CONVERSIONS) && tabSwitcher(CONVERSIONS, 'Goals')} - {isEnabled(PROPS) && tabSwitcher(PROPS, 'Properties')} - {isEnabled(FUNNELS) && - Funnel && - (hasFunnels() ? tabFunnelPicker() : tabSwitcher(FUNNELS, 'Funnels'))} -
- ) } function afterFetchData(apiResponse) { @@ -443,24 +341,63 @@ export default function Behaviours({ importedDataInView }) { } } - if (mode) { - return ( -
-
-
-
-

- {sectionTitle() + (isRealtime() ? ' (last 30min)' : '')} -

- {renderImportedQueryUnsupportedWarning()} -
- {tabs()} -
- {renderContent()} -
-
- ) - } else { + if (!mode) { return null } + + return ( +
+
+
+
+

+ {sectionTitle() + (isRealtime() ? ' (last 30min)' : '')} +

+ {renderImportedQueryUnsupportedWarning()} +
+ + {isEnabled(CONVERSIONS) && ( + + Goals + + )} + {isEnabled(PROPS) && ( + + Properties + + )} + {isEnabled(FUNNELS) && + Funnel && + (site.funnels.length > 0 && site.funnelsAvailable ? ( + ({ + label: name, + onClick: setFunnelFactory(name), + selected: mode === FUNNELS && selectedFunnel === name + }))} + collectionTitle="Funnels" + searchable={true} + > + Funnels + + ) : ( + + Funnels + + ))} + +
+ {renderContent()} +
+
+ ) } diff --git a/assets/js/dashboard/stats/devices/index.js b/assets/js/dashboard/stats/devices/index.js index ca9dd8ae2f..d9a05d0a0f 100644 --- a/assets/js/dashboard/stats/devices/index.js +++ b/assets/js/dashboard/stats/devices/index.js @@ -19,6 +19,7 @@ import { operatingSystemVersionsRoute, screenSizesRoute } from '../../router' +import { TabButton, TabWrapper } from '../../components/tabs' // Icons copied from https://github.com/alrra/browser-logos const BROWSER_ICONS = { @@ -430,27 +431,6 @@ export default function Devices() { } } - function renderPill(name, pill) { - const isActive = mode === pill - - if (isActive) { - return ( - - ) - } - - return ( - - ) - } - return (
@@ -461,11 +441,21 @@ export default function Devices() { skipImportedReason={skipImportedReason} />
-
- {renderPill('Browser', 'browser')} - {renderPill('OS', 'os')} - {renderPill('Size', 'size')} -
+ + {[ + { label: 'Browser', value: 'browser' }, + { label: 'OS', value: 'os' }, + { label: 'Size', value: 'size' } + ].map(({ label, value }) => ( + switchTab(value)} + > + {label} + + ))} +
{renderContent()}
diff --git a/assets/js/dashboard/stats/graph/interval-picker.js b/assets/js/dashboard/stats/graph/interval-picker.js deleted file mode 100644 index c5c4b89e08..0000000000 --- a/assets/js/dashboard/stats/graph/interval-picker.js +++ /dev/null @@ -1,173 +0,0 @@ -import React, { Fragment, useRef } from 'react' -import { Menu, Transition } from '@headlessui/react' -import { ChevronDownIcon } from '@heroicons/react/20/solid' -import classNames from 'classnames' -import * as storage from '../../util/storage' -import { - BlurMenuButtonOnEscape, - isModifierPressed, - isTyping, - Keybind -} from '../../keybinding' -import { useQueryContext } from '../../query-context' -import { useSiteContext } from '../../site-context' -import { useMatch } from 'react-router-dom' -import { rootRoute } from '../../router' -import { popover } from '../../components/popover' - -const INTERVAL_LABELS = { - minute: 'Minutes', - hour: 'Hours', - day: 'Days', - week: 'Weeks', - month: 'Months' -} - -function validIntervals(site, query) { - if (query.period === 'custom') { - if (query.to.diff(query.from, 'days') < 7) { - return ['day'] - } else if (query.to.diff(query.from, 'months') < 1) { - return ['day', 'week'] - } else if (query.to.diff(query.from, 'months') < 12) { - return ['day', 'week', 'month'] - } else { - return ['week', 'month'] - } - } else { - return site.validIntervalsByPeriod[query.period] - } -} - -function getDefaultInterval(query, validIntervals) { - const defaultByPeriod = { - day: 'hour', - '7d': 'day', - '6mo': 'month', - '12mo': 'month', - year: 'month' - } - - if (query.period === 'custom') { - return defaultForCustomPeriod(query.from, query.to) - } else { - return defaultByPeriod[query.period] || validIntervals[0] - } -} - -function defaultForCustomPeriod(from, to) { - if (to.diff(from, 'days') < 30) { - return 'day' - } else if (to.diff(from, 'months') < 6) { - return 'week' - } else { - return 'month' - } -} - -function getStoredInterval(period, domain) { - const stored = storage.getItem(`interval__${period}__${domain}`) - - if (stored === 'date') { - return 'day' - } else { - return stored - } -} - -function storeInterval(period, domain, interval) { - storage.setItem(`interval__${period}__${domain}`, interval) -} - -export const getCurrentInterval = function (site, query) { - const options = validIntervals(site, query) - - const storedInterval = getStoredInterval(query.period, site.domain) - const defaultInterval = getDefaultInterval(query, options) - - if (storedInterval && options.includes(storedInterval)) { - return storedInterval - } else { - return defaultInterval - } -} - -export function IntervalPicker({ onIntervalUpdate }) { - const menuElement = useRef(null) - const { query } = useQueryContext() - const site = useSiteContext() - const dashboardRouteMatch = useMatch(rootRoute.path) - - if (query.period == 'realtime') return null - - const options = validIntervals(site, query) - const currentInterval = getCurrentInterval(site, query) - - function updateInterval(interval) { - storeInterval(query.period, site.domain, interval) - onIntervalUpdate(interval) - } - - function renderDropdownItem(option) { - return ( - updateInterval(option)} - key={option} - disabled={option == currentInterval} - > - {({ active }) => ( - - {INTERVAL_LABELS[option]} - - )} - - ) - } - - return ( - - {({ open }) => ( - <> - {!!dashboardRouteMatch && ( - { - menuElement.current?.click() - }} - shouldIgnoreWhen={[isModifierPressed, isTyping]} - /> - )} - - - {INTERVAL_LABELS[currentInterval]} - - - - - {options.map(renderDropdownItem)} - - - - )} - - ) -} diff --git a/assets/js/dashboard/stats/graph/interval-picker.tsx b/assets/js/dashboard/stats/graph/interval-picker.tsx new file mode 100644 index 0000000000..e1f53b350f --- /dev/null +++ b/assets/js/dashboard/stats/graph/interval-picker.tsx @@ -0,0 +1,190 @@ +import React, { useRef } from 'react' +import { Popover, Transition } from '@headlessui/react' +import { ChevronDownIcon } from '@heroicons/react/20/solid' +import classNames from 'classnames' +import * as storage from '../../util/storage' +import { isModifierPressed, isTyping, Keybind } from '../../keybinding' +import { useQueryContext } from '../../query-context' +import { useSiteContext, PlausibleSite } from '../../site-context' +import { useMatch } from 'react-router-dom' +import { rootRoute } from '../../router' +import { BlurMenuButtonOnEscape, popover } from '../../components/popover' +import { DashboardQuery } from '../../query' +import { Dayjs } from 'dayjs' +import { QueryPeriod } from '../../query-time-periods' + +const INTERVAL_LABELS: Record = { + minute: 'Minutes', + hour: 'Hours', + day: 'Days', + week: 'Weeks', + month: 'Months' +} + +function validIntervals(site: PlausibleSite, query: DashboardQuery): string[] { + if (query.period === QueryPeriod.custom && query.from && query.to) { + if (query.to.diff(query.from, 'days') < 7) { + return ['day'] + } else if (query.to.diff(query.from, 'months') < 1) { + return ['day', 'week'] + } else if (query.to.diff(query.from, 'months') < 12) { + return ['day', 'week', 'month'] + } else { + return ['week', 'month'] + } + } else { + return site.validIntervalsByPeriod[query.period] + } +} + +function getDefaultInterval( + query: DashboardQuery, + validIntervals: string[] +): string { + const defaultByPeriod: Record = { + day: 'hour', + '7d': 'day', + '6mo': 'month', + '12mo': 'month', + year: 'month' + } + + if (query.period === QueryPeriod.custom && query.from && query.to) { + return defaultForCustomPeriod(query.from, query.to) + } else { + return defaultByPeriod[query.period] || validIntervals[0] + } +} + +function defaultForCustomPeriod(from: Dayjs, to: Dayjs): string { + if (to.diff(from, 'days') < 30) { + return 'day' + } else if (to.diff(from, 'months') < 6) { + return 'week' + } else { + return 'month' + } +} + +function getStoredInterval(period: string, domain: string): string | null { + const stored = storage.getItem(`interval__${period}__${domain}`) + + if (stored === 'date') { + return 'day' + } else { + return stored + } +} + +function storeInterval(period: string, domain: string, interval: string): void { + storage.setItem(`interval__${period}__${domain}`, interval) +} + +export const getCurrentInterval = function ( + site: PlausibleSite, + query: DashboardQuery +): string { + const options = validIntervals(site, query) + + const storedInterval = getStoredInterval(query.period, site.domain) + const defaultInterval = getDefaultInterval(query, options) + + if (storedInterval && options.includes(storedInterval)) { + return storedInterval + } else { + return defaultInterval + } +} + +export function IntervalPicker({ + onIntervalUpdate +}: { + onIntervalUpdate: (interval: string) => void +}): JSX.Element | null { + const menuElement = useRef(null) + const { query } = useQueryContext() + const site = useSiteContext() + const dashboardRouteMatch = useMatch(rootRoute.path) + + if (query.period == 'realtime') { + return null + } + + const options = validIntervals(site, query) + const currentInterval = getCurrentInterval(site, query) + + function updateInterval(interval: string): void { + storeInterval(query.period, site.domain, interval) + onIntervalUpdate(interval) + } + + return ( + <> + {!!dashboardRouteMatch && ( + { + menuElement.current?.click() + }} + shouldIgnoreWhen={[isModifierPressed, isTyping]} + /> + )} + + {({ close: closeDropdown }) => ( + <> + + + {INTERVAL_LABELS[currentInterval]} + + + + + {options.map((option) => ( + + ))} + + + + )} + + + ) +} diff --git a/assets/js/dashboard/stats/locations/index.js b/assets/js/dashboard/stats/locations/index.js index ec95cf2662..a2941a6633 100644 --- a/assets/js/dashboard/stats/locations/index.js +++ b/assets/js/dashboard/stats/locations/index.js @@ -15,6 +15,7 @@ import ImportedQueryUnsupportedWarning from '../imported-query-unsupported-warni import { citiesRoute, countriesRoute, regionsRoute } from '../../router' import { useQueryContext } from '../../query-context' import { useSiteContext } from '../../site-context' +import { TabButton, TabWrapper } from '../../components/tabs' function Countries({ query, site, onClick, afterFetchData }) { function fetchData() { @@ -244,27 +245,6 @@ class Locations extends React.Component { } } - renderPill(name, mode) { - const isActive = this.state.mode === mode - - if (isActive) { - return ( - - ) - } - - return ( - - ) - } - render() { return (
@@ -278,12 +258,22 @@ class Locations extends React.Component { skipImportedReason={this.state.skipImportedReason} />
-
- {this.renderPill('Map', 'map')} - {this.renderPill('Countries', 'countries')} - {this.renderPill('Regions', 'regions')} - {this.renderPill('Cities', 'cities')} -
+ + {[ + { label: 'Map', value: 'map' }, + { label: 'Countries', value: 'countries' }, + { label: 'Regions', value: 'regions' }, + { label: 'Cities', value: 'cities' } + ].map(({ value, label }) => ( + + {label} + + ))} +
{this.renderContent()}
diff --git a/assets/js/dashboard/stats/modals/breakdown-table.tsx b/assets/js/dashboard/stats/modals/breakdown-table.tsx index 4a5281768d..9079a8fca4 100644 --- a/assets/js/dashboard/stats/modals/breakdown-table.tsx +++ b/assets/js/dashboard/stats/modals/breakdown-table.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react' +import React, { ReactNode, useRef } from 'react' import { SearchInput } from '../../components/search-input' import { ColumnConfiguraton, Table } from '../../components/table' @@ -34,36 +34,41 @@ export const BreakdownTable = ({ error?: Error | null /** Controls whether the component displays API request errors or ignores them. */ displayError?: boolean -}) => ( -
-
-
-

{title}

- {!isPending && isFetching && } +}) => { + const searchRef = useRef(null) + + return ( +
+
+
+

{title}

+ {!isPending && isFetching && } +
+ {!!onSearch && ( + + )} +
+
+
+ {displayError && status === 'error' && } + {isPending && } + {data && data={data} columns={columns} />} + {!isPending && !isFetching && hasNextPage && ( + fetchNextPage()} + isFetchingNextPage={isFetchingNextPage} + /> + )}
- {!!onSearch && ( - - )}
-
-
- {displayError && status === 'error' && } - {isPending && } - {data && data={data} columns={columns} />} - {!isPending && !isFetching && hasNextPage && ( - fetchNextPage()} - isFetchingNextPage={isFetchingNextPage} - /> - )} -
-
-) + ) +} const InitialLoadingSpinner = () => (
- {name} - - ) - } - - return ( - - ) - } - return (
{/* Header Container */} @@ -219,11 +199,21 @@ export default function Pages() { skipImportedReason={skipImportedReason} />
-
- {renderPill('Top Pages', 'pages')} - {renderPill('Entry Pages', 'entry-pages')} - {renderPill('Exit Pages', 'exit-pages')} -
+ + {[ + { label: 'Top Pages', value: 'pages' }, + { label: 'Entry Pages', value: 'entry-pages' }, + { label: 'Exit Pages', value: 'exit-pages' } + ].map(({ value, label }) => ( + switchTab(value)} + key={value} + > + {label} + + ))} +
{/* Main Contents */} {renderContent()} diff --git a/assets/js/dashboard/stats/sources/source-list.js b/assets/js/dashboard/stats/sources/source-list.js index a814db5fb8..a7abc45c4c 100644 --- a/assets/js/dashboard/stats/sources/source-list.js +++ b/assets/js/dashboard/stats/sources/source-list.js @@ -1,4 +1,4 @@ -import React, { Fragment, useEffect, useRef, useState } from 'react' +import React, { useEffect, useState } from 'react' import * as storage from '../../util/storage' import * as url from '../../util/url' @@ -10,9 +10,6 @@ import { getFiltersByKeyPrefix, hasConversionGoalFilter } from '../../util/filters' -import { Menu, Transition } from '@headlessui/react' -import { ChevronDownIcon } from '@heroicons/react/20/solid' -import classNames from 'classnames' import ImportedQueryUnsupportedWarning from '../imported-query-unsupported-warning' import { useQueryContext } from '../../query-context' import { useSiteContext } from '../../site-context' @@ -25,7 +22,7 @@ import { utmSourcesRoute, utmTermsRoute } from '../../router' -import { BlurMenuButtonOnEscape } from '../../keybinding' +import { DropdownTabButton, TabButton, TabWrapper } from '../../components/tabs' const UTM_TAGS = { utm_medium: { @@ -197,7 +194,6 @@ export default function SourceList() { const [loading, setLoading] = useState(true) const [skipImportedReason, setSkipImportedReason] = useState(null) const previousQuery = usePrevious(query) - const dropdownButtonRef = useRef(null) useEffect(() => setLoading(true), [query, currentTab]) @@ -224,105 +220,24 @@ export default function SourceList() { } } - function renderTabs() { - const activeClass = - 'inline-block h-5 text-indigo-700 dark:text-indigo-500 font-bold active-prop-heading truncate text-left' - const defaultClass = - 'hover:text-indigo-600 cursor-pointer truncate text-left' - const dropdownOptions = Object.keys(UTM_TAGS) - let buttonText = UTM_TAGS[currentTab] - ? UTM_TAGS[currentTab].title - : 'Campaigns' - - return ( -
-
- Channels -
-
- Sources -
- - - -
- - - {buttonText} - - -
- - - -
- {dropdownOptions.map((option) => { - return ( - - {({ active }) => ( - - {UTM_TAGS[option].title} - - )} - - ) - })} -
-
-
-
-
- ) - } - function onChannelClick() { setTab('all')() } function renderContent() { - if (currentTab === 'all') { - return - } else if (currentTab == 'channels') { - return ( - - ) - } else { + if (Object.keys(UTM_TAGS).includes(currentTab)) { return } + + switch (currentTab) { + case 'channels': + return ( + + ) + case 'all': + default: + return + } } function afterFetchData(apiResponse) { @@ -343,7 +258,33 @@ export default function SourceList() { skipImportedReason={skipImportedReason} />
- {renderTabs()} + + {[ + { value: 'channels', label: 'Channels' }, + { value: 'all', label: 'Sources' } + ].map(({ value, label }) => ( + + {label} + + ))} + ({ + value, + label: title, + onClick: setTab(value), + selected: currentTab === value + }))} + > + {UTM_TAGS[currentTab] ? UTM_TAGS[currentTab].title : 'Campaigns'} + +
{/* Main Contents */} {renderContent()} diff --git a/assets/package-lock.json b/assets/package-lock.json index b2e5079ab7..6013a9eaba 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -9,8 +9,8 @@ "version": "1.4.0", "license": "AGPL-3.0-or-later", "dependencies": { - "@headlessui/react": "^1.7.10", - "@heroicons/react": "^2.0.11", + "@headlessui/react": "^1.7.19", + "@heroicons/react": "^2.2.0", "@jsonurl/jsonurl": "^1.1.7", "@juggle/resize-observer": "^3.3.1", "@popperjs/core": "^2.11.6", @@ -56,18 +56,19 @@ "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest": "^28.11.0", "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "globals": "^16.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jsdom-testing-mocks": "^1.13.1", "json-schema-to-typescript": "^15.0.2", "prettier": "^3.3.3", - "stylelint": "^16.8.1", + "stylelint": "^16.17.0", "stylelint-config-standard": "^36.0.1", "ts-jest": "^29.2.4", "typescript": "^5.5.4", - "typescript-eslint": "^8.28.0" + "typescript-eslint": "^8.29.0" } }, "node_modules/@adobe/css-tools": { @@ -118,12 +119,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -266,18 +268,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -293,40 +295,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -513,24 +500,25 @@ } }, "node_modules/@babel/runtime": { - "version": "7.21.5", - "license": "MIT", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" @@ -564,14 +552,13 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -584,9 +571,9 @@ "dev": true }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", - "integrity": "sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", "dev": true, "funding": [ { @@ -598,17 +585,18 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.4.1" + "@csstools/css-tokenizer": "^3.0.3" } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz", - "integrity": "sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", "dev": true, "funding": [ { @@ -620,14 +608,15 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=18" } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.13", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz", - "integrity": "sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", "dev": true, "funding": [ { @@ -639,34 +628,13 @@ "url": "https://opencollective.com/csstools" } ], + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", - "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^6.0.13" + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" } }, "node_modules/@dual-bundle/import-meta-resolve": { @@ -836,9 +804,12 @@ } }, "node_modules/@headlessui/react": { - "version": "1.7.10", + "version": "1.7.19", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.19.tgz", + "integrity": "sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==", "license": "MIT", "dependencies": { + "@tanstack/react-virtual": "^3.0.0-beta.60", "client-only": "^0.0.1" }, "engines": { @@ -850,10 +821,11 @@ } }, "node_modules/@heroicons/react": { - "version": "2.0.11", - "license": "MIT", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", "peerDependencies": { - "react": ">= 16" + "react": ">= 16 || ^19.0.0-rc" } }, "node_modules/@humanfs/core": { @@ -1132,21 +1104,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@jest/console/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1163,45 +1120,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/core": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", @@ -1249,21 +1167,6 @@ } } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1280,45 +1183,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -1434,21 +1298,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@jest/reporters/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1465,45 +1314,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -1586,21 +1396,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@jest/transform/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1617,51 +1412,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/transform/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jest/transform/node_modules/write-file-atomic": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", @@ -1692,21 +1448,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1723,45 +1464,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -1820,6 +1522,16 @@ "version": "3.3.1", "license": "Apache-2.0" }, + "node_modules/@keyv/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz", @@ -1971,6 +1683,31 @@ "react": "^18.0.0" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.5.tgz", + "integrity": "sha512-MzSSMGkFWCDSb2xXqmdbfQqBG4wcRI3JKVjpYGZG0CccnViLpfRW4tGU97ImfBbSYzvEWJ/2SK/OiIoSmcUBAA==", + "dependencies": { + "@tanstack/virtual-core": "3.13.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.5.tgz", + "integrity": "sha512-gMLNylxhJdUlfRR1G3U9rtuwUh2IjdrrniJIDcekVJN3/3i+bluvdMi3+eodnxzJq5nKnxnigo9h0lIpaqV6HQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -1990,21 +1727,6 @@ "node": ">=18" } }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@testing-library/dom/node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -2030,33 +1752,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@testing-library/dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@testing-library/dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/dom/node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -2089,18 +1784,6 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, - "node_modules/@testing-library/dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/jest-dom": { "version": "6.4.8", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz", @@ -2122,21 +1805,6 @@ "yarn": ">=1" } }, - "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@testing-library/jest-dom/node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -2150,51 +1818,12 @@ "node": ">=8" } }, - "node_modules/@testing-library/jest-dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", "dev": true }, - "node_modules/@testing-library/jest-dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/react": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.0.tgz", @@ -2729,16 +2358,17 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", - "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz", + "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/type-utils": "8.28.0", - "@typescript-eslint/utils": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/type-utils": "8.29.0", + "@typescript-eslint/utils": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2758,15 +2388,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", - "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz", + "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4" }, "engines": { @@ -2782,13 +2413,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", - "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", + "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0" + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2799,13 +2431,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", - "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz", + "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/utils": "8.29.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -2822,10 +2455,11 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", - "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2835,13 +2469,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", - "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2865,6 +2500,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -2874,6 +2510,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2889,6 +2526,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -2897,15 +2535,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", - "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz", + "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0" + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2920,12 +2559,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", - "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/types": "8.29.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2941,6 +2581,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3282,14 +2923,19 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/any-promise": { @@ -3497,6 +3143,7 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3576,21 +3223,6 @@ "@babel/core": "^7.8.0" } }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/babel-jest/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3607,45 +3239,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -3736,6 +3329,34 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==", + "dev": true, + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.2.0", "license": "MIT", @@ -3816,12 +3437,58 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/cacheable": { + "version": "1.8.9", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.9.tgz", + "integrity": "sha512-FicwAUyWnrtnd4QqYAoRlNs44/a1jTL7XDKqm5gJ90wz1DQPlC7U2Rd1Tydpv+E7WAr4sQHuw8Q8M3nZMAyecQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.7.1", + "keyv": "^5.3.1" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", + "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.0.3" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3915,19 +3582,6 @@ } ] }, - "node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -4012,6 +3666,8 @@ }, "node_modules/client-only": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, "node_modules/cliui": { @@ -4045,15 +3701,22 @@ "dev": true }, "node_modules/color-convert": { - "version": "1.9.3", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, @@ -4140,21 +3803,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/create-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/create-jest/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4171,45 +3819,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/create-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/create-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/create-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/create-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4225,21 +3834,30 @@ } }, "node_modules/css-functions-list": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.2.tgz", - "integrity": "sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", + "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12 || >=16" } }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "node_modules/css-mediaquery": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", + "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==", "dev": true, + "license": "BSD" + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -5172,14 +4790,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/escodegen": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", @@ -5460,10 +5070,11 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.37.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", - "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==", + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, + "license": "MIT", "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -5475,7 +5086,7 @@ "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.8", + "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", @@ -5559,20 +5170,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -5588,22 +5185,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "dev": true, @@ -5627,16 +5208,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/espree": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", @@ -5779,15 +5350,16 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -5815,10 +5387,21 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", - "dev": true + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fastest-levenshtein": { "version": "1.0.16", @@ -5931,10 +5514,11 @@ "license": "MIT" }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" }, "node_modules/for-each": { "version": "0.3.5", @@ -6291,11 +5875,13 @@ } }, "node_modules/has-flag": { - "version": "3.0.0", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { @@ -6363,6 +5949,13 @@ "node": ">= 0.4" } }, + "node_modules/hookified": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.8.1.tgz", + "integrity": "sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA==", + "dev": true, + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -6439,6 +6032,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/iframe-resizer": { "version": "4.3.2", "license": "MIT", @@ -7042,27 +6656,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", @@ -7140,21 +6733,6 @@ "node": ">=10" } }, - "node_modules/jake/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jake/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7171,45 +6749,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jake/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jake/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jake/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -7281,21 +6820,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-circus/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7312,45 +6836,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -7384,21 +6869,6 @@ } } }, - "node_modules/jest-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-cli/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7415,45 +6885,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-config": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", @@ -7499,21 +6930,6 @@ } } }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-config/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7530,45 +6946,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -7584,21 +6961,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-diff/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7615,45 +6977,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-docblock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", @@ -7682,21 +7005,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-each/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7713,45 +7021,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-environment-jsdom": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", @@ -7858,21 +7127,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-matcher-utils/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7889,45 +7143,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-message-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", @@ -7948,21 +7163,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7979,45 +7179,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -8091,21 +7252,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-resolve/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8122,45 +7268,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runner": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", @@ -8193,21 +7300,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-runner/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8224,45 +7316,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runtime": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", @@ -8296,21 +7349,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-runtime/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8327,33 +7365,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runtime/node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -8363,18 +7374,6 @@ "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", @@ -8406,21 +7405,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-snapshot/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8437,33 +7421,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-snapshot/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -8476,18 +7433,6 @@ "node": ">=10" } }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -8505,21 +7450,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8536,45 +7466,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-validate": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", @@ -8592,21 +7483,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -8635,45 +7511,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-watcher": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", @@ -8693,21 +7530,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-watcher/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8724,45 +7546,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -8778,15 +7561,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -8871,6 +7645,20 @@ } } }, + "node_modules/jsdom-testing-mocks": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/jsdom-testing-mocks/-/jsdom-testing-mocks-1.13.1.tgz", + "integrity": "sha512-8BAsnuoO4DLGTf7LDbSm8fcx5CUHSv4h+bdUbwyt6rMYAXWjeHLRx9f8sYiSxoOTXy3S1e06pe87KER39o1ckA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bezier-easing": "^2.1.0", + "css-mediaquery": "^0.1.2" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -9027,10 +7815,11 @@ } }, "node_modules/known-css-properties": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", - "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", - "dev": true + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", + "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", + "dev": true, + "license": "MIT" }, "node_modules/language-subtag-registry": { "version": "0.3.23", @@ -9127,7 +7916,8 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", @@ -9222,10 +8012,11 @@ } }, "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" }, "node_modules/meow": { "version": "13.2.0", @@ -9355,9 +8146,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -9469,14 +8260,16 @@ } }, "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -9735,9 +8528,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -9838,9 +8632,9 @@ } }, "node_modules/postcss": { - "version": "8.4.40", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", - "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "funding": [ { "type": "opencollective", @@ -9855,10 +8649,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -9953,15 +8748,16 @@ } }, "node_modules/postcss-resolve-nested-selector": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.4.tgz", - "integrity": "sha512-R6vHqZWgVnTAPq0C+xjyHfEZqfIYboCBVSy24MjxEDm+tIh1BU4O6o7DP7AA7kHzf136d+Qc5duI4tlpHjixDw==", - "dev": true + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "dev": true, + "license": "MIT" }, "node_modules/postcss-safe-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz", - "integrity": "sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", "dev": true, "funding": [ { @@ -9977,6 +8773,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "engines": { "node": ">=18.0" }, @@ -9988,6 +8785,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -10299,8 +9097,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "license": "MIT" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", @@ -10336,6 +9135,7 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10701,6 +9501,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -10713,39 +9514,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -10756,9 +9524,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -10969,6 +9738,8 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -11034,9 +9805,9 @@ } }, "node_modules/stylelint": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.8.1.tgz", - "integrity": "sha512-O8aDyfdODSDNz/B3gW2HQ+8kv8pfhSu7ZR7xskQ93+vI6FhKKGUJMQ03Ydu+w3OvXXE0/u4hWU4hCPNOyld+OA==", + "version": "16.17.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.17.0.tgz", + "integrity": "sha512-I9OwVIWRMqVm2Br5iTbrfSqGRPWQUlvm6oXO1xZuYYu0Gpduy67N8wXOZv15p6E/JdlZiAtQaIoLKZEWk5hrjw==", "dev": true, "funding": [ { @@ -11048,45 +9819,45 @@ "url": "https://github.com/sponsors/stylelint" } ], + "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^2.7.1", - "@csstools/css-tokenizer": "^2.4.1", - "@csstools/media-query-list-parser": "^2.1.13", - "@csstools/selector-specificity": "^3.1.1", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2", + "@csstools/selector-specificity": "^5.0.0", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", - "css-functions-list": "^3.2.2", - "css-tree": "^2.3.1", - "debug": "^4.3.6", - "fast-glob": "^3.3.2", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.3.7", + "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^9.0.0", + "file-entry-cache": "^10.0.7", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^5.3.1", + "ignore": "^7.0.3", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.34.0", + "known-css-properties": "^0.35.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", - "micromatch": "^4.0.7", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", - "picocolors": "^1.0.1", - "postcss": "^8.4.40", - "postcss-resolve-nested-selector": "^0.1.4", - "postcss-safe-parser": "^7.0.0", - "postcss-selector-parser": "^6.1.1", + "picocolors": "^1.1.1", + "postcss": "^8.5.3", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", - "strip-ansi": "^7.1.0", - "supports-hyperlinks": "^3.0.0", + "supports-hyperlinks": "^3.2.0", "svg-tags": "^1.0.0", - "table": "^6.8.2", + "table": "^6.9.0", "write-file-atomic": "^5.0.1" }, "bin": { @@ -11143,16 +9914,27 @@ "stylelint": "^16.1.0" } }, - "node_modules/stylelint/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/stylelint/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=12" + "node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" } }, "node_modules/stylelint/node_modules/balanced-match": { @@ -11161,28 +9943,49 @@ "license": "MIT" }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.0.0.tgz", - "integrity": "sha512-6MgEugi8p2tiUhqO7GnPsmbCCzj0YRCwwaTbpGRyKZesjRSzkqkAE9fPp7V2yMs5hwfgbQLgdvSSkGNg1s5Uvw==", + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.0.7.tgz", + "integrity": "sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^5.0.0" - }, - "engines": { - "node": ">=18" + "flat-cache": "^6.1.7" } }, "node_modules/stylelint/node_modules/flat-cache": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", - "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.7.tgz", + "integrity": "sha512-qwZ4xf1v1m7Rc9XiORly31YaChvKt6oNVHuqqZcoED/7O+ToyNVGobKsIAopY9ODcWpEDKEBAbrSOCBHtNQvew==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.3.1", - "keyv": "^4.5.4" + "cacheable": "^1.8.9", + "flatted": "^3.3.3", + "hookified": "^1.7.1" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=18" + "node": ">=4" } }, "node_modules/stylelint/node_modules/resolve-from": { @@ -11193,21 +9996,6 @@ "node": ">=8" } }, - "node_modules/stylelint/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/sucrase": { "version": "3.34.0", "license": "MIT", @@ -11257,48 +10045,33 @@ } }, "node_modules/supports-color": { - "version": "5.5.0", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-hyperlinks": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", - "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, "engines": { "node": ">=14.18" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -11322,10 +10095,11 @@ "dev": true }, "node_modules/table": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", - "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -11342,6 +10116,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11357,7 +10132,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tailwindcss": { "version": "3.3.3", @@ -11476,15 +10252,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11758,14 +10525,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.28.0.tgz", - "integrity": "sha512-jfZtxJoHm59bvoCMYCe2BM0/baMswRhMmYhy+w6VfcyHrjxZ0OJe0tGasydCpIpA+A/WIJhTyZfb3EtwNC/kHQ==", + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.0.tgz", + "integrity": "sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.28.0", - "@typescript-eslint/parser": "8.28.0", - "@typescript-eslint/utils": "8.28.0" + "@typescript-eslint/eslint-plugin": "8.29.0", + "@typescript-eslint/parser": "8.29.0", + "@typescript-eslint/utils": "8.29.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -12129,72 +10897,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/wrappy": { "version": "1.0.2", "license": "ISC" diff --git a/assets/package.json b/assets/package.json index 798387cbd3..ad73805031 100644 --- a/assets/package.json +++ b/assets/package.json @@ -13,8 +13,8 @@ "generate-types": "json2ts ../priv/json-schemas/query-api-schema.json ../assets/js/types/query-api.d.ts --bannerComment '/* Autogenerated, recreate with `npm run --prefix assets generate-types` */'" }, "dependencies": { - "@headlessui/react": "^1.7.10", - "@heroicons/react": "^2.0.11", + "@headlessui/react": "^1.7.19", + "@heroicons/react": "^2.2.0", "@jsonurl/jsonurl": "^1.1.7", "@juggle/resize-observer": "^3.3.1", "@popperjs/core": "^2.11.6", @@ -60,18 +60,19 @@ "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest": "^28.11.0", "eslint-plugin-jsx-a11y": "^6.10.2", - "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "globals": "^16.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jsdom-testing-mocks": "^1.13.1", "json-schema-to-typescript": "^15.0.2", "prettier": "^3.3.3", - "stylelint": "^16.8.1", + "stylelint": "^16.17.0", "stylelint-config-standard": "^36.0.1", "ts-jest": "^29.2.4", "typescript": "^5.5.4", - "typescript-eslint": "^8.28.0" + "typescript-eslint": "^8.29.0" }, "name": "assets" } diff --git a/assets/test-utils/jsdom-mocks.ts b/assets/test-utils/jsdom-mocks.ts new file mode 100644 index 0000000000..324a8b1a24 --- /dev/null +++ b/assets/test-utils/jsdom-mocks.ts @@ -0,0 +1,5 @@ +import { configMocks } from 'jsdom-testing-mocks' +import { act } from '@testing-library/react' + +// as per jsdom-testing-mocks docs, this is needed to avoid having to wrap everything in act calls +configMocks({ act }) diff --git a/lib/plausible_web/plugs/favicon.ex b/lib/plausible_web/plugs/favicon.ex index b5c0f49e5f..88e4338dd5 100644 --- a/lib/plausible_web/plugs/favicon.ex +++ b/lib/plausible_web/plugs/favicon.ex @@ -29,7 +29,7 @@ defmodule PlausibleWeb.Favicon do import Plug.Conn alias Plausible.HTTPClient - @placeholder_icon_location "priv/placeholder_favicon.ico" + @placeholder_icon_location "priv/placeholder_favicon.svg" @placeholder_icon File.read!(@placeholder_icon_location) @custom_icons %{ "Brave" => "search.brave.com", @@ -65,8 +65,7 @@ defmodule PlausibleWeb.Favicon do I'm not sure why DDG sometimes returns a broken PNG image in their response but we filter that out. When the icon request fails, we show a placeholder - favicon instead. The placeholder is an emoji from - [https://favicon.io/emoji-favicons/](https://favicon.io/emoji-favicons/) + favicon instead. The placeholder is an svg from [https://heroicons.com/](https://heroicons.com/). DuckDuckGo favicon service has some issues with [SVG favicons](https://css-tricks.com/svg-favicons-and-all-the-fun-things-we-can-do-with-them/). For some reason, they return them with `content-type=image/x-icon` whereas SVG @@ -123,7 +122,7 @@ defmodule PlausibleWeb.Favicon do defp send_placeholder(conn) do conn - |> put_resp_content_type("image/x-icon") + |> put_resp_content_type("image/svg+xml") |> put_resp_header("cache-control", "public, max-age=2592000") |> send_resp(200, @placeholder_icon) |> halt diff --git a/priv/placeholder_favicon.svg b/priv/placeholder_favicon.svg new file mode 100644 index 0000000000..87f903bd37 --- /dev/null +++ b/priv/placeholder_favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/plausible_web/plugs/favicon_test.exs b/test/plausible_web/plugs/favicon_test.exs index 064c52f7fe..228c0ab9ec 100644 --- a/test/plausible_web/plugs/favicon_test.exs +++ b/test/plausible_web/plugs/favicon_test.exs @@ -145,7 +145,7 @@ defmodule PlausibleWeb.FaviconTest do end describe "Fallback to placeholder icon" do - @placholder_icon File.read!("priv/placeholder_favicon.ico") + @placeholder_icon File.read!("priv/placeholder_favicon.svg") test "falls back to placeholder when DDG returns a non-2xx response", %{plug_opts: plug_opts} do expect( @@ -163,7 +163,7 @@ defmodule PlausibleWeb.FaviconTest do assert conn.halted assert conn.status == 200 - assert conn.resp_body == @placholder_icon + assert conn.resp_body == @placeholder_icon end test "falls back to placeholder in case of a network error", %{plug_opts: plug_opts} do @@ -181,7 +181,7 @@ defmodule PlausibleWeb.FaviconTest do assert conn.halted assert conn.status == 200 - assert conn.resp_body == @placholder_icon + assert conn.resp_body == @placeholder_icon end test "falls back to placeholder when DDG returns a broken image response", %{ @@ -201,7 +201,7 @@ defmodule PlausibleWeb.FaviconTest do assert conn.halted assert conn.status == 200 - assert conn.resp_body == @placholder_icon + assert conn.resp_body == @placeholder_icon end end end