@boxcustodia/library 2.0.0-alpha.11 → 2.0.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +38 -38
- package/dist/index.css +1 -1
- package/dist/index.d.ts +28 -29
- package/dist/index.es.js +6804 -6792
- package/package.json +4 -3
- package/src/__doc__/Changelog.mdx +6 -0
- package/src/__doc__/Components.mdx +73 -0
- package/src/__doc__/Examples.tsx +69 -0
- package/src/__doc__/Icons.mdx +41 -0
- package/src/__doc__/Intro.mdx +138 -0
- package/src/__doc__/MCP.mdx +71 -0
- package/src/__doc__/Migration.mdx +451 -0
- package/src/__doc__/Theme.mdx +132 -0
- package/src/__doc__/Types.mdx +252 -0
- package/src/components/alert/alert.stories.tsx +142 -0
- package/src/components/alert/alert.tsx +109 -0
- package/src/components/alert/index.ts +7 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +173 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +49 -0
- package/src/components/alert-dialog/alert-dialog.tsx +265 -0
- package/src/components/alert-dialog/index.ts +1 -0
- package/src/components/auto-complete/auto-complete-primitives.tsx +155 -0
- package/src/components/auto-complete/auto-complete.stories.tsx +241 -0
- package/src/components/auto-complete/auto-complete.tsx +82 -0
- package/src/components/auto-complete/index.ts +2 -0
- package/src/components/avatar/avatar.stories.tsx +84 -0
- package/src/components/avatar/avatar.test.tsx +61 -0
- package/src/components/avatar/avatar.tsx +104 -0
- package/src/components/avatar/index.ts +1 -0
- package/src/components/background-image/background-image.stories.tsx +21 -0
- package/src/components/background-image/background-image.test.tsx +29 -0
- package/src/components/background-image/background-image.tsx +23 -0
- package/src/components/background-image/index.ts +1 -0
- package/src/components/button/button.stories.tsx +396 -0
- package/src/components/button/button.test.tsx +58 -0
- package/src/components/button/button.tsx +31 -0
- package/src/components/button/button.variants.ts +44 -0
- package/src/components/button/components/base-button.tsx +86 -0
- package/src/components/button/components/loader-overlay.tsx +21 -0
- package/src/components/button/components/loading-icon.tsx +47 -0
- package/src/components/button/index.ts +3 -0
- package/src/components/calendar/calendar.model.ts +86 -0
- package/src/components/calendar/calendar.stories.tsx +155 -0
- package/src/components/calendar/calendar.test.tsx +12 -0
- package/src/components/calendar/calendar.tsx +185 -0
- package/src/components/calendar/components/calendar-navigation.tsx +141 -0
- package/src/components/calendar/components/day.tsx +61 -0
- package/src/components/calendar/components/decade-view.tsx +45 -0
- package/src/components/calendar/components/index.ts +6 -0
- package/src/components/calendar/components/month-view.tsx +58 -0
- package/src/components/calendar/components/week-days.tsx +27 -0
- package/src/components/calendar/components/year-view.tsx +29 -0
- package/src/components/calendar/hooks/index.ts +4 -0
- package/src/components/calendar/hooks/use-calendar-navigation.ts +79 -0
- package/src/components/calendar/hooks/use-calendar.ts +90 -0
- package/src/components/calendar/hooks/use-multiple-calendar.ts +34 -0
- package/src/components/calendar/hooks/use-range-calendar.ts +91 -0
- package/src/components/calendar/hooks/use-single-calendar.ts +18 -0
- package/src/components/calendar/index.ts +1 -0
- package/src/components/calendar/utils/typeguards.ts +7 -0
- package/src/components/card/card.stories.tsx +116 -0
- package/src/components/card/card.tsx +74 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/center/center.stories.tsx +81 -0
- package/src/components/center/center.tsx +24 -0
- package/src/components/center/index.ts +1 -0
- package/src/components/checkbox/checkbox.stories.tsx +307 -0
- package/src/components/checkbox/checkbox.tsx +273 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/checkbox-group/checkbox-group.stories.tsx +104 -0
- package/src/components/checkbox-group/checkbox-group.tsx +16 -0
- package/src/components/checkbox-group/index.ts +1 -0
- package/src/components/combobox/combobox.stories.tsx +339 -0
- package/src/components/combobox/combobox.tsx +898 -0
- package/src/components/combobox/index.ts +1 -0
- package/src/components/date-picker/date-input.stories.tsx +158 -0
- package/src/components/date-picker/date-input.tsx +163 -0
- package/src/components/date-picker/date-picker.model.ts +90 -0
- package/src/components/date-picker/date-picker.stories.tsx +200 -0
- package/src/components/date-picker/date-picker.test.tsx +23 -0
- package/src/components/date-picker/date-picker.tsx +298 -0
- package/src/components/date-picker/date-picker.utils.ts +260 -0
- package/src/components/date-picker/index.ts +3 -0
- package/src/components/date-picker/use-date-input-popover.ts +48 -0
- package/src/components/date-picker/use-date-input.ts +125 -0
- package/src/components/dialog/dialog.stories.tsx +171 -0
- package/src/components/dialog/dialog.test.tsx +68 -0
- package/src/components/dialog/dialog.tsx +277 -0
- package/src/components/dialog/index.ts +1 -0
- package/src/components/divider/divider.stories.tsx +139 -0
- package/src/components/divider/divider.test.tsx +22 -0
- package/src/components/divider/divider.tsx +23 -0
- package/src/components/divider/index.ts +1 -0
- package/src/components/dropzone/dropzone.stories.tsx +210 -0
- package/src/components/dropzone/dropzone.tsx +154 -0
- package/src/components/dropzone/file-types.ts +64 -0
- package/src/components/dropzone/index.ts +3 -0
- package/src/components/dropzone/upload-primitives.tsx +310 -0
- package/src/components/dropzone/use-dropzone.ts +122 -0
- package/src/components/empty-state/empty-state.stories.tsx +56 -0
- package/src/components/empty-state/empty-state.tsx +39 -0
- package/src/components/empty-state/index.ts +1 -0
- package/src/components/field/field.stories.tsx +223 -0
- package/src/components/field/field.tsx +229 -0
- package/src/components/field/index.ts +1 -0
- package/src/components/form/form.stories.tsx +594 -0
- package/src/components/form/form.tsx +30 -0
- package/src/components/form/index.ts +1 -0
- package/src/components/heading/heading.stories.tsx +74 -0
- package/src/components/heading/heading.tsx +28 -0
- package/src/components/heading/heading.variants.ts +27 -0
- package/src/components/heading/index.ts +1 -0
- package/src/components/index.ts +46 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.stories.tsx +104 -0
- package/src/components/input/input.tsx +75 -0
- package/src/components/kbd/index.ts +1 -0
- package/src/components/kbd/kbd.stories.tsx +40 -0
- package/src/components/kbd/kbd.tsx +31 -0
- package/src/components/kbd/kbd.variants.ts +26 -0
- package/src/components/label/index.ts +1 -0
- package/src/components/label/label.stories.tsx +68 -0
- package/src/components/label/label.test.tsx +61 -0
- package/src/components/label/label.tsx +62 -0
- package/src/components/loader/index.ts +1 -0
- package/src/components/loader/loader.stories.tsx +60 -0
- package/src/components/loader/loader.test.tsx +26 -0
- package/src/components/loader/loader.tsx +60 -0
- package/src/components/menu/index.ts +2 -0
- package/src/components/menu/menu-primitives.tsx +248 -0
- package/src/components/menu/menu.stories.tsx +203 -0
- package/src/components/menu/menu.tsx +100 -0
- package/src/components/menu/util/render-menu-item.tsx +54 -0
- package/src/components/multi-select/hooks/use-multi-select.ts +66 -0
- package/src/components/multi-select/index.ts +1 -0
- package/src/components/multi-select/multi-select.stories.tsx +294 -0
- package/src/components/multi-select/multi-select.tsx +300 -0
- package/src/components/multi-select/multi-select.variants.ts +22 -0
- package/src/components/number-input/index.ts +1 -0
- package/src/components/number-input/number-input.stories.tsx +209 -0
- package/src/components/number-input/number-input.test.tsx +87 -0
- package/src/components/number-input/number-input.tsx +232 -0
- package/src/components/pagination/components/pagination-option.tsx +27 -0
- package/src/components/pagination/index.ts +1 -0
- package/src/components/pagination/pagination.stories.tsx +80 -0
- package/src/components/pagination/pagination.test.tsx +76 -0
- package/src/components/pagination/pagination.tsx +102 -0
- package/src/components/password/index.ts +1 -0
- package/src/components/password/password.stories.tsx +104 -0
- package/src/components/password/password.tsx +75 -0
- package/src/components/popover/index.ts +1 -0
- package/src/components/popover/popover.stories.tsx +213 -0
- package/src/components/popover/popover.tsx +203 -0
- package/src/components/progress/index.ts +1 -0
- package/src/components/progress/progress.stories.tsx +124 -0
- package/src/components/progress/progress.test.tsx +25 -0
- package/src/components/progress/progress.tsx +124 -0
- package/src/components/scroll-area/index.ts +1 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +166 -0
- package/src/components/scroll-area/scroll-area.tsx +64 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.stories.tsx +253 -0
- package/src/components/select/select.tsx +430 -0
- package/src/components/show/index.ts +1 -0
- package/src/components/show/show.stories.tsx +197 -0
- package/src/components/show/show.test.tsx +41 -0
- package/src/components/show/show.tsx +16 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.stories.tsx +36 -0
- package/src/components/skeleton/skeleton.test.tsx +14 -0
- package/src/components/skeleton/skeleton.tsx +15 -0
- package/src/components/stack/index.ts +1 -0
- package/src/components/stack/stack.stories.tsx +194 -0
- package/src/components/stack/stack.tsx +52 -0
- package/src/components/stepper/Stepper.tsx +190 -0
- package/src/components/stepper/context/stepper-context.tsx +11 -0
- package/src/components/stepper/index.ts +1 -0
- package/src/components/stepper/stepper.stories.tsx +130 -0
- package/src/components/stepper/stepper.test.tsx +91 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.stories.tsx +122 -0
- package/src/components/switch/switch.test.tsx +30 -0
- package/src/components/switch/switch.tsx +86 -0
- package/src/components/table/index.ts +3 -0
- package/src/components/table/table-primitives.tsx +122 -0
- package/src/components/table/table.model.ts +20 -0
- package/src/components/table/table.stories.tsx +169 -0
- package/src/components/table/table.test.tsx +91 -0
- package/src/components/table/table.tsx +109 -0
- package/src/components/table-pagination/index.ts +2 -0
- package/src/components/table-pagination/table-pagination.model.ts +2 -0
- package/src/components/table-pagination/table-pagination.stories.tsx +23 -0
- package/src/components/table-pagination/table-pagination.test.tsx +32 -0
- package/src/components/table-pagination/table-pagination.tsx +108 -0
- package/src/components/tabs/context/tabs-context.tsx +14 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.stories.tsx +182 -0
- package/src/components/tabs/tabs.test.tsx +61 -0
- package/src/components/tabs/tabs.tsx +175 -0
- package/src/components/tag/index.ts +2 -0
- package/src/components/tag/tag.stories.tsx +170 -0
- package/src/components/tag/tag.test.tsx +18 -0
- package/src/components/tag/tag.tsx +99 -0
- package/src/components/tag/tag.variants.ts +31 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.stories.tsx +73 -0
- package/src/components/textarea/textarea.tsx +105 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/timeline-status.ts +5 -0
- package/src/components/timeline/timeline.stories.tsx +84 -0
- package/src/components/timeline/timeline.tsx +147 -0
- package/src/components/toast/index.ts +1 -0
- package/src/components/toast/toast.stories.tsx +392 -0
- package/src/components/toast/toast.test.tsx +50 -0
- package/src/components/toast/toast.tsx +411 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/tooltip.stories.tsx +226 -0
- package/src/components/tooltip/tooltip.test.tsx +46 -0
- package/src/components/tooltip/tooltip.tsx +171 -0
- package/src/components/tree/hooks/use-controllable-tree-state.ts +80 -0
- package/src/components/tree/index.ts +2 -0
- package/src/components/tree/tree-primitives.tsx +126 -0
- package/src/components/tree/tree.stories.tsx +468 -0
- package/src/components/tree/tree.tsx +42 -0
- package/src/hooks/index.ts +26 -0
- package/src/hooks/useArray/__doc__/useArray.stories.tsx +100 -0
- package/src/hooks/useArray/__test__/useArray.test.tsx +88 -0
- package/src/hooks/useArray/index.ts +1 -0
- package/src/hooks/useArray/useArray.ts +76 -0
- package/src/hooks/useAsync/__doc__/useAsync.stories.tsx +149 -0
- package/src/hooks/useAsync/__test__/useAsync.test.tsx +68 -0
- package/src/hooks/useAsync/index.ts +1 -0
- package/src/hooks/useAsync/useAsync.ts +58 -0
- package/src/hooks/useClickOutside/__doc__/useClickOutside.stories.tsx +40 -0
- package/src/hooks/useClickOutside/__test__/useClickOutside.test.tsx +33 -0
- package/src/hooks/useClickOutside/index.ts +1 -0
- package/src/hooks/useClickOutside/useClickOutside.ts +26 -0
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +45 -0
- package/src/hooks/useClipboard/__test__/useClipboard.test.tsx +19 -0
- package/src/hooks/useClipboard/index.ts +1 -0
- package/src/hooks/useClipboard/useClipboard.tsx +28 -0
- package/src/hooks/useDebounceCallback/__doc__/useDebouncedCallback.stories.tsx +84 -0
- package/src/hooks/useDebounceCallback/index.ts +1 -0
- package/src/hooks/useDebounceCallback/useDebouncedCallback.ts +23 -0
- package/src/hooks/useDebounceValue/__doc__/useDebouncedValue.stories.tsx +75 -0
- package/src/hooks/useDebounceValue/index.ts +1 -0
- package/src/hooks/useDebounceValue/useDebouncedValue.ts +17 -0
- package/src/hooks/useDisclosure/__doc__/useDisclosure.stories.tsx +39 -0
- package/src/hooks/useDisclosure/__test__/useDisclosure.test.ts +43 -0
- package/src/hooks/useDisclosure/index.ts +1 -0
- package/src/hooks/useDisclosure/useDisclosure.ts +37 -0
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +26 -0
- package/src/hooks/useDocumentTitle/index.ts +1 -0
- package/src/hooks/useDocumentTitle/useDocumentTitle.tsx +11 -0
- package/src/hooks/useEventListener/__doc__/useEventListener.stories.tsx +28 -0
- package/src/hooks/useEventListener/__test__/useEventListener.test.tsx +26 -0
- package/src/hooks/useEventListener/index.ts +1 -0
- package/src/hooks/useEventListener/useEventListener.ts +25 -0
- package/src/hooks/useFocusTrap/__doc__/useFocusTrap.stories.tsx +37 -0
- package/src/hooks/useFocusTrap/index.ts +1 -0
- package/src/hooks/useFocusTrap/scopeTab.ts +38 -0
- package/src/hooks/useFocusTrap/tabbable.ts +70 -0
- package/src/hooks/useFocusTrap/useFocusTrap.ts +78 -0
- package/src/hooks/useHotkey/__docs__/useHotkey.stories.tsx +116 -0
- package/src/hooks/useHotkey/__test__/useHotkey.test.tsx +105 -0
- package/src/hooks/useHotkey/__utils__/create-hotkey-listener.ts +25 -0
- package/src/hooks/useHotkey/__utils__/index.ts +3 -0
- package/src/hooks/useHotkey/__utils__/is-input-field.ts +14 -0
- package/src/hooks/useHotkey/__utils__/match-key-modifiers.ts +25 -0
- package/src/hooks/useHotkey/index.ts +1 -0
- package/src/hooks/useHotkey/useHotkey.ts +34 -0
- package/src/hooks/useHover/__doc__/useHover.stories.tsx +41 -0
- package/src/hooks/useHover/__test__/useHover.test.tsx +45 -0
- package/src/hooks/useHover/index.ts +1 -0
- package/src/hooks/useHover/useHover.tsx +40 -0
- package/src/hooks/useIsVisible/__doc__/useIsVisible.stories.tsx +60 -0
- package/src/hooks/useIsVisible/index.ts +1 -0
- package/src/hooks/useIsVisible/useIsVisible.tsx +50 -0
- package/src/hooks/useLocalStorage/__doc__/useLocalStorage.stories.tsx +86 -0
- package/src/hooks/useLocalStorage/__test__/useLocalStorage.test.ts +85 -0
- package/src/hooks/useLocalStorage/index.ts +1 -0
- package/src/hooks/useLocalStorage/useLocalStorage.ts +57 -0
- package/src/hooks/useMediaQuery/__doc__/useMediaQuery.stories.tsx +39 -0
- package/src/hooks/useMediaQuery/index.ts +1 -0
- package/src/hooks/useMediaQuery/useMediaQuery.ts +22 -0
- package/src/hooks/useMemoizedFn/index.ts +1 -0
- package/src/hooks/useMemoizedFn/useMemoizedFn.ts +32 -0
- package/src/hooks/useMutation/__doc__/useMutation.stories.tsx +111 -0
- package/src/hooks/useMutation/__test__/useMutation.test.tsx +83 -0
- package/src/hooks/useMutation/index.ts +1 -0
- package/src/hooks/useMutation/useMutation.tsx +60 -0
- package/src/hooks/useObject/__doc__/useObject.stories.tsx +119 -0
- package/src/hooks/useObject/__test__/useObject.test.tsx +87 -0
- package/src/hooks/useObject/index.ts +1 -0
- package/src/hooks/useObject/useObject.tsx +48 -0
- package/src/hooks/usePagination/__doc__/usePagination.stories.tsx +72 -0
- package/src/hooks/usePagination/__test__/usePagination.test.tsx +98 -0
- package/src/hooks/usePagination/index.ts +2 -0
- package/src/hooks/usePagination/usePagination.tsx +74 -0
- package/src/hooks/usePortal/__doc__/usePortal.stories.tsx +19 -0
- package/src/hooks/usePortal/__test__/usePortal.test.tsx +20 -0
- package/src/hooks/usePortal/index.ts +1 -0
- package/src/hooks/usePortal/usePortal.ts +40 -0
- package/src/hooks/usePreventCloseWindow/__doc__/usePreventCloseWindow.stories.tsx +32 -0
- package/src/hooks/usePreventCloseWindow/index.ts +1 -0
- package/src/hooks/usePreventCloseWindow/usePreventCloseWindow.ts +33 -0
- package/src/hooks/useRangePagination/__test__/useRangePagination.test.tsx +63 -0
- package/src/hooks/useRangePagination/index.ts +2 -0
- package/src/hooks/useRangePagination/useRangePagination.tsx +72 -0
- package/src/hooks/useSelection/__doc__/useSelection.stories.tsx +140 -0
- package/src/hooks/useSelection/__test__/useSelection.test.tsx +57 -0
- package/src/hooks/useSelection/index.ts +1 -0
- package/src/hooks/useSelection/useSelection.ts +121 -0
- package/src/hooks/useStep/__doc__/useStep.stories.tsx +98 -0
- package/src/hooks/useStep/__test__/useStep.test.ts +51 -0
- package/src/hooks/useStep/index.ts +1 -0
- package/src/hooks/useStep/useStep.ts +57 -0
- package/src/hooks/useToggle/__doc__/useToggle.stories.tsx +25 -0
- package/src/hooks/useToggle/__test__/useToggle.test.tsx +43 -0
- package/src/hooks/useToggle/index.ts +1 -0
- package/src/hooks/useToggle/useToggle.ts +16 -0
- package/src/index.ts +6 -0
- package/src/lib/cn.ts +8 -0
- package/src/lib/index.ts +1 -0
- package/src/models/Generic.model.ts +67 -0
- package/src/models/index.ts +1 -0
- package/src/providers/index.ts +2 -0
- package/src/providers/library-provider.tsx +44 -0
- package/src/providers/theme/ThemeProvider.tsx +25 -0
- package/src/providers/theme/index.ts +3 -0
- package/src/providers/theme/types.ts +11 -0
- package/src/providers/theme/useThemeProps.ts +25 -0
- package/src/stores/theme.store.ts +31 -0
- package/src/styles/components.css +4 -0
- package/src/styles/index.css +2 -0
- package/src/styles/library.css +2 -0
- package/src/styles/theme.css +232 -0
- package/src/utils/dates/parseDateRange.utility.ts +39 -0
- package/src/utils/form.tsx +91 -0
- package/src/utils/functions/createSafeContext.ts +17 -0
- package/src/utils/functions/ensureReactElement.tsx +30 -0
- package/src/utils/functions/getFormData.ts +19 -0
- package/src/utils/functions/index.ts +4 -0
- package/src/utils/functions/mergeRefs.ts +18 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/strings/extractInitials.utility.ts +10 -0
- package/src/utils/strings/index.ts +1 -0
- package/src/utils/tests/click.ts +3 -0
- package/src/utils/tests/index.ts +2 -0
- package/src/utils/tests/keyboard.ts +21 -0
- package/src/utils/tests/type.ts +6 -0
- package/dist/components.css +0 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Heading, Input } from "../../../components";
|
|
3
|
+
import { createToastManager, ToastProvider } from "../../../components/toast";
|
|
4
|
+
import { useDebouncedCallback } from "../../useDebounceCallback";
|
|
5
|
+
|
|
6
|
+
const toastManager = createToastManager();
|
|
7
|
+
|
|
8
|
+
const meta: Meta = {
|
|
9
|
+
title: "hooks/useDebouncedCallback",
|
|
10
|
+
decorators: [
|
|
11
|
+
(Story) => (
|
|
12
|
+
<ToastProvider toastManager={toastManager}>
|
|
13
|
+
<Story />
|
|
14
|
+
</ToastProvider>
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
|
|
21
|
+
export const Basic = {
|
|
22
|
+
render: () => {
|
|
23
|
+
const handleSearch = useDebouncedCallback((value: string) => {
|
|
24
|
+
console.log("Búsqueda con:", value);
|
|
25
|
+
toastManager.add({
|
|
26
|
+
variant: "success",
|
|
27
|
+
description: `Búsqueda realizada con: ${value}`,
|
|
28
|
+
});
|
|
29
|
+
}, 600);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="space-y-2">
|
|
33
|
+
<pre className="rounded-md bg-slate-950 p-4 text-white">
|
|
34
|
+
<code className="block">
|
|
35
|
+
Escribe en el input para ver el callback debounced en acción.
|
|
36
|
+
</code>
|
|
37
|
+
<code className="block">Revisa la consola para ver los logs.</code>
|
|
38
|
+
</pre>
|
|
39
|
+
<Input
|
|
40
|
+
onChange={(value) => handleSearch(value)}
|
|
41
|
+
placeholder="Escribe para buscar..."
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const DifferentDelays: StoryObj = {
|
|
49
|
+
render: () => {
|
|
50
|
+
const handleFastDebounce = useDebouncedCallback((value: string) => {
|
|
51
|
+
toastManager.add({
|
|
52
|
+
variant: "success",
|
|
53
|
+
description: `Debounce rápido (200ms): ${value}`,
|
|
54
|
+
});
|
|
55
|
+
}, 200);
|
|
56
|
+
|
|
57
|
+
const handleSlowDebounce = useDebouncedCallback((value: string) => {
|
|
58
|
+
toastManager.add({
|
|
59
|
+
variant: "success",
|
|
60
|
+
description: `Debounce lento (1000ms): ${value}`,
|
|
61
|
+
});
|
|
62
|
+
}, 1000);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="space-y-4">
|
|
66
|
+
<div>
|
|
67
|
+
<Heading>Debounce Rápido (200ms)</Heading>
|
|
68
|
+
<Input
|
|
69
|
+
onChange={(value) => handleFastDebounce(value)}
|
|
70
|
+
placeholder="Debounce rápido..."
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div>
|
|
75
|
+
<Heading>Debounce Lento (1000ms)</Heading>
|
|
76
|
+
<Input
|
|
77
|
+
onChange={(value) => handleSlowDebounce(value)}
|
|
78
|
+
placeholder="Debounce lento..."
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useDebouncedCallback } from "./useDebouncedCallback";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useCallback, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
function useDebouncedCallback<T extends (...args: any[]) => any>(
|
|
4
|
+
callback: T,
|
|
5
|
+
delay: number,
|
|
6
|
+
): (...args: Parameters<T>) => void {
|
|
7
|
+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
8
|
+
|
|
9
|
+
return useCallback(
|
|
10
|
+
(...args: Parameters<T>) => {
|
|
11
|
+
if (timeoutRef.current) {
|
|
12
|
+
clearTimeout(timeoutRef.current);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
timeoutRef.current = setTimeout(() => {
|
|
16
|
+
callback(...args);
|
|
17
|
+
}, delay);
|
|
18
|
+
},
|
|
19
|
+
[callback, delay],
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default useDebouncedCallback;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Input } from "../../../components";
|
|
4
|
+
import { useDebouncedValue } from "../../useDebounceValue";
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: "hooks/useDebouncedValue",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
export const Basic = {
|
|
13
|
+
render: () => {
|
|
14
|
+
const [value, setValue] = useState<string>("");
|
|
15
|
+
const debouncedValue: string = useDebouncedValue(value, 400);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="space-y-2">
|
|
19
|
+
<pre className="rounded-md bg-slate-950 p-4 text-white">
|
|
20
|
+
<code className="block">
|
|
21
|
+
Valor actual: <span className="text-blue-300">{value}</span>
|
|
22
|
+
</code>
|
|
23
|
+
<code className="block">
|
|
24
|
+
Valor debounced:{" "}
|
|
25
|
+
<span className="text-blue-300">{debouncedValue}</span>
|
|
26
|
+
</code>
|
|
27
|
+
</pre>
|
|
28
|
+
<Input
|
|
29
|
+
value={value}
|
|
30
|
+
onChange={setValue}
|
|
31
|
+
placeholder="Escribe algo..."
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const MultipleDelays: StoryObj = {
|
|
39
|
+
render: () => {
|
|
40
|
+
const [value, setValue] = useState<string>("");
|
|
41
|
+
const debouncedFast: string = useDebouncedValue(value, 200);
|
|
42
|
+
const debouncedMedium: string = useDebouncedValue(value, 500);
|
|
43
|
+
const debouncedSlow: string = useDebouncedValue(value, 1000);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="space-y-4">
|
|
47
|
+
<Input
|
|
48
|
+
value={value}
|
|
49
|
+
onChange={setValue}
|
|
50
|
+
placeholder="Escribe para ver diferentes delays..."
|
|
51
|
+
/>
|
|
52
|
+
|
|
53
|
+
<div className="space-y-2">
|
|
54
|
+
<pre className="rounded-md bg-slate-950 p-4 text-white">
|
|
55
|
+
<code className="block">
|
|
56
|
+
Valor actual: <span className="text-blue-300">{value}</span>
|
|
57
|
+
</code>
|
|
58
|
+
<code className="block">
|
|
59
|
+
Debounced (200ms):{" "}
|
|
60
|
+
<span className="text-blue-300">{debouncedFast}</span>
|
|
61
|
+
</code>
|
|
62
|
+
<code className="block">
|
|
63
|
+
Debounced (500ms):{" "}
|
|
64
|
+
<span className="text-blue-300">{debouncedMedium}</span>
|
|
65
|
+
</code>
|
|
66
|
+
<code className="block">
|
|
67
|
+
Debounced (1000ms):{" "}
|
|
68
|
+
<span className="text-blue-300">{debouncedSlow}</span>
|
|
69
|
+
</code>
|
|
70
|
+
</pre>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useDebouncedValue } from "./useDebouncedValue";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
function useDebouncedValue<T>(value: T, delay: number): T {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const timeout = setTimeout(() => {
|
|
8
|
+
setDebouncedValue(value);
|
|
9
|
+
}, delay);
|
|
10
|
+
|
|
11
|
+
return () => clearTimeout(timeout);
|
|
12
|
+
}, [value, delay]);
|
|
13
|
+
|
|
14
|
+
return debouncedValue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default useDebouncedValue;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Meta } from "@storybook/react-vite";
|
|
2
|
+
import { Button } from "../../../components";
|
|
3
|
+
import { cn } from "../../../lib";
|
|
4
|
+
import useDisclosure from "../useDisclosure";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook que facilita el manejo de un estado booleano. Provee métodos para abrir, cerrar y toggle el estado
|
|
8
|
+
*/
|
|
9
|
+
const meta: Meta = {
|
|
10
|
+
title: "hooks/useDisclosure",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
export const Default = {
|
|
16
|
+
render: () => {
|
|
17
|
+
const [isOpen, { open, close, toggle }] = useDisclosure();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div>
|
|
21
|
+
<div
|
|
22
|
+
onClick={toggle}
|
|
23
|
+
className={cn(
|
|
24
|
+
"rounded transition-colors w-32 h-32 shadow text-3xl grid place-items-center cursor-pointer",
|
|
25
|
+
{ "bg-accent": isOpen },
|
|
26
|
+
)}
|
|
27
|
+
>
|
|
28
|
+
🍅
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div className="flex gap-2 mt-2">
|
|
32
|
+
<Button onClick={open}>Abrir</Button>
|
|
33
|
+
<Button onClick={close}>Cerrar</Button>
|
|
34
|
+
<Button onClick={toggle}>Toggle</Button>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import useDisclosure from "../useDisclosure";
|
|
4
|
+
|
|
5
|
+
describe("useDisclosure hook", () => {
|
|
6
|
+
it("should be defined", () => {
|
|
7
|
+
const { result } = renderHook(useDisclosure);
|
|
8
|
+
expect(result.current).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should open", () => {
|
|
12
|
+
const { result } = renderHook(useDisclosure);
|
|
13
|
+
const [state, { open }] = result.current;
|
|
14
|
+
expect(state).toBe(false);
|
|
15
|
+
act(() => open());
|
|
16
|
+
|
|
17
|
+
waitFor(() => {
|
|
18
|
+
expect(state).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should close", () => {
|
|
23
|
+
const { result } = renderHook(useDisclosure);
|
|
24
|
+
const [state, { close }] = result.current;
|
|
25
|
+
expect(state).toBe(false);
|
|
26
|
+
act(() => close());
|
|
27
|
+
|
|
28
|
+
waitFor(() => {
|
|
29
|
+
expect(state).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should toggle", () => {
|
|
34
|
+
const { result } = renderHook(useDisclosure);
|
|
35
|
+
const [state, { toggle }] = result.current;
|
|
36
|
+
expect(state).toBe(false);
|
|
37
|
+
act(() => toggle());
|
|
38
|
+
|
|
39
|
+
waitFor(() => {
|
|
40
|
+
expect(state).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useDisclosure } from "./useDisclosure";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
|
|
3
|
+
function useDisclosure(
|
|
4
|
+
initialState = false,
|
|
5
|
+
callbacks?: { onOpen?: () => void; onClose?: () => void },
|
|
6
|
+
) {
|
|
7
|
+
const { onOpen, onClose } = callbacks || {};
|
|
8
|
+
const [isOpen, setIsOpen] = useState(initialState);
|
|
9
|
+
|
|
10
|
+
const open = useCallback(() => {
|
|
11
|
+
setIsOpen((isOpened) => {
|
|
12
|
+
if (!isOpened) {
|
|
13
|
+
onOpen?.();
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return isOpened;
|
|
17
|
+
});
|
|
18
|
+
}, [onOpen]);
|
|
19
|
+
|
|
20
|
+
const close = useCallback(() => {
|
|
21
|
+
setIsOpen((isOpened) => {
|
|
22
|
+
if (isOpened) {
|
|
23
|
+
onClose?.();
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return isOpened;
|
|
27
|
+
});
|
|
28
|
+
}, [onClose]);
|
|
29
|
+
|
|
30
|
+
const toggle = useCallback(() => {
|
|
31
|
+
isOpen ? close() : open();
|
|
32
|
+
}, [close, open, isOpen]);
|
|
33
|
+
|
|
34
|
+
return [isOpen, { open, close, toggle }] as const;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default useDisclosure;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Button, Input } from "../../../components";
|
|
4
|
+
import useDocumentTitle from "../useDocumentTitle";
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: "hooks/useDocumentTitle",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => {
|
|
16
|
+
const [title, setTitle] = useState("");
|
|
17
|
+
useDocumentTitle(title);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="space-y-2">
|
|
21
|
+
<Input onChange={setTitle} value={title} />
|
|
22
|
+
<Button onClick={() => setTitle(title)}>Set document title</Button>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useDocumentTitle } from "./useDocumentTitle";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Meta } from "@storybook/react-vite";
|
|
2
|
+
import { action } from "storybook/actions";
|
|
3
|
+
import { Button } from "../../../components";
|
|
4
|
+
import useEventListener from "../useEventListener";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook que facilita el manejor de eventos
|
|
8
|
+
*
|
|
9
|
+
* Evita tener que hacer el `useEffect` con el cleanup
|
|
10
|
+
*/
|
|
11
|
+
const meta: Meta = {
|
|
12
|
+
title: "hooks/useEventListener",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ```tsx
|
|
19
|
+
* const ref = useEventListener<HTMLELEMENT>("EVENT_KEY", callback);
|
|
20
|
+
* return <HTMLELEMENT ref={ref} />;
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const Default = {
|
|
24
|
+
render: () => {
|
|
25
|
+
const ref = useEventListener("click", action("click"));
|
|
26
|
+
return <Button ref={ref}>click me</Button>;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { screen } from "@testing-library/dom";
|
|
2
|
+
import { render, waitFor } from "@testing-library/react";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { click } from "../../../utils";
|
|
5
|
+
import useEventListener from "../useEventListener";
|
|
6
|
+
|
|
7
|
+
describe("useEventListener hook", () => {
|
|
8
|
+
it("should be defined", () => {
|
|
9
|
+
expect(useEventListener).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should handle event", () => {
|
|
13
|
+
const handleEvent = vi.fn();
|
|
14
|
+
const Component = () => {
|
|
15
|
+
const ref = useEventListener("click", handleEvent);
|
|
16
|
+
return <div ref={ref} data-testid="target" />;
|
|
17
|
+
};
|
|
18
|
+
render(<Component />);
|
|
19
|
+
|
|
20
|
+
click(screen.getByTestId("target"));
|
|
21
|
+
|
|
22
|
+
waitFor(() => {
|
|
23
|
+
expect(handleEvent).toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useEventListener } from "./useEventListener";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
function useEventListener<
|
|
4
|
+
K extends keyof HTMLElementEventMap,
|
|
5
|
+
T extends HTMLElement = any,
|
|
6
|
+
>(
|
|
7
|
+
type: K,
|
|
8
|
+
listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => any,
|
|
9
|
+
options?: boolean | AddEventListenerOptions,
|
|
10
|
+
) {
|
|
11
|
+
const ref = useRef<T | null>(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const target = ref.current ?? document;
|
|
15
|
+
|
|
16
|
+
target.addEventListener(type, listener as EventListener, options);
|
|
17
|
+
return () => {
|
|
18
|
+
target.removeEventListener(type, listener as EventListener, options);
|
|
19
|
+
};
|
|
20
|
+
}, [type, listener, options]);
|
|
21
|
+
|
|
22
|
+
return ref;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default useEventListener;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Meta } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Button, Checkbox } from "../../../components";
|
|
4
|
+
import useFocusTrap from "../useFocusTrap";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook que atrapa el focus en un scope
|
|
8
|
+
*/
|
|
9
|
+
const meta: Meta = {
|
|
10
|
+
title: "hooks/useFocusTrap",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
export const Default = {
|
|
16
|
+
render: () => {
|
|
17
|
+
const [active, setActive] = useState(true);
|
|
18
|
+
const trapRefContainer = useFocusTrap();
|
|
19
|
+
const trapRef = useFocusTrap(active);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div ref={trapRefContainer} className="space-y-2">
|
|
23
|
+
<div className="py-4">
|
|
24
|
+
<Checkbox name="1" />
|
|
25
|
+
<div ref={trapRef}>
|
|
26
|
+
<Checkbox name="2" />
|
|
27
|
+
<Checkbox name="3" />
|
|
28
|
+
</div>
|
|
29
|
+
<Checkbox name="4" />
|
|
30
|
+
</div>
|
|
31
|
+
<Button onClick={() => setActive(!active)}>
|
|
32
|
+
{active ? "Desactivar" : "Activar"}
|
|
33
|
+
</Button>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useFocusTrap } from "./useFocusTrap";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { findTabbableDescendants } from "./tabbable";
|
|
2
|
+
|
|
3
|
+
export function scopeTab(node: HTMLElement, event: KeyboardEvent) {
|
|
4
|
+
const tabbable = findTabbableDescendants(node);
|
|
5
|
+
if (!tabbable.length) {
|
|
6
|
+
event.preventDefault();
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const finalTabbable = tabbable[event.shiftKey ? 0 : tabbable.length - 1];
|
|
10
|
+
const root = node.getRootNode() as unknown as DocumentOrShadowRoot;
|
|
11
|
+
let leavingFinalTabbable =
|
|
12
|
+
finalTabbable === root.activeElement || node === root.activeElement;
|
|
13
|
+
|
|
14
|
+
const activeElement = root.activeElement as Element;
|
|
15
|
+
const activeElementIsRadio =
|
|
16
|
+
activeElement.tagName === "INPUT" &&
|
|
17
|
+
activeElement.getAttribute("type") === "radio";
|
|
18
|
+
if (activeElementIsRadio) {
|
|
19
|
+
const activeRadioGroup = tabbable.filter(
|
|
20
|
+
(element) =>
|
|
21
|
+
element.getAttribute("type") === "radio" &&
|
|
22
|
+
element.getAttribute("name") === activeElement.getAttribute("name"),
|
|
23
|
+
);
|
|
24
|
+
leavingFinalTabbable = activeRadioGroup.includes(finalTabbable);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!leavingFinalTabbable) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
|
|
33
|
+
const target = tabbable[event.shiftKey ? tabbable.length - 1 : 0];
|
|
34
|
+
|
|
35
|
+
if (target) {
|
|
36
|
+
(target as HTMLElement).focus();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const TABBABLE_NODES = /input|select|textarea|button|object/;
|
|
2
|
+
export const FOCUS_SELECTOR =
|
|
3
|
+
"a, input, select, textarea, button, object, [tabindex]";
|
|
4
|
+
|
|
5
|
+
function hidden(element: HTMLElement) {
|
|
6
|
+
if (process.env.NODE_ENV === "test") {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return element.style.display === "none";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function visible(element: HTMLElement) {
|
|
14
|
+
const isHidden =
|
|
15
|
+
element.getAttribute("aria-hidden") ||
|
|
16
|
+
element.getAttribute("hidden") ||
|
|
17
|
+
element.getAttribute("type") === "hidden";
|
|
18
|
+
|
|
19
|
+
if (isHidden) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let parentElement: HTMLElement = element;
|
|
24
|
+
while (parentElement) {
|
|
25
|
+
if (parentElement === document.body || parentElement.nodeType === 11) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (hidden(parentElement)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
parentElement = parentElement.parentNode as HTMLElement;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getElementTabIndex(element: HTMLElement) {
|
|
40
|
+
let tabIndex: string | null | undefined = element.getAttribute("tabindex");
|
|
41
|
+
if (tabIndex === null) {
|
|
42
|
+
tabIndex = undefined;
|
|
43
|
+
}
|
|
44
|
+
return parseInt(tabIndex as string, 10);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function focusable(element: HTMLElement) {
|
|
48
|
+
const nodeName = element.nodeName.toLowerCase();
|
|
49
|
+
const isTabIndexNotNaN = !Number.isNaN(getElementTabIndex(element));
|
|
50
|
+
const res =
|
|
51
|
+
// @ts-expect-error function accepts any html element but if it is a button, it should not be disabled to trigger the condition
|
|
52
|
+
(TABBABLE_NODES.test(nodeName) && !element.disabled) ||
|
|
53
|
+
(element instanceof HTMLAnchorElement
|
|
54
|
+
? element.href || isTabIndexNotNaN
|
|
55
|
+
: isTabIndexNotNaN);
|
|
56
|
+
|
|
57
|
+
return res && visible(element);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function tabbable(element: HTMLElement) {
|
|
61
|
+
const tabIndex = getElementTabIndex(element);
|
|
62
|
+
const isTabIndexNaN = Number.isNaN(tabIndex);
|
|
63
|
+
return (isTabIndexNaN || tabIndex >= 0) && focusable(element);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function findTabbableDescendants(element: HTMLElement): HTMLElement[] {
|
|
67
|
+
return Array.from(
|
|
68
|
+
element.querySelectorAll<HTMLElement>(FOCUS_SELECTOR),
|
|
69
|
+
).filter(tabbable);
|
|
70
|
+
}
|