@nofinite/nui 1.0.2 → 1.1.1
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/components/accordion/Accordion.cjs.map +1 -1
- package/dist/components/accordion/Accordion.js.map +1 -1
- package/dist/components/alert/Alert.cjs.map +1 -1
- package/dist/components/alert/Alert.js.map +1 -1
- package/dist/components/avatar/Avatar.cjs.map +1 -1
- package/dist/components/avatar/Avatar.js.map +1 -1
- package/dist/components/avatar/AvatarGroup.cjs.map +1 -1
- package/dist/components/avatar/AvatarGroup.js.map +1 -1
- package/dist/components/badge/Badge.cjs.map +1 -1
- package/dist/components/badge/Badge.js.map +1 -1
- package/dist/components/badge/BadgeGroup.cjs.map +1 -1
- package/dist/components/badge/BadgeGroup.js.map +1 -1
- package/dist/components/breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/button/Button.cjs.map +1 -1
- package/dist/components/button/Button.js.map +1 -1
- package/dist/components/card/Card.cjs.map +1 -1
- package/dist/components/card/Card.js.map +1 -1
- package/dist/components/checkbox/Checkbox.cjs.map +1 -1
- package/dist/components/checkbox/Checkbox.js.map +1 -1
- package/dist/components/chip/Chip.cjs.map +1 -1
- package/dist/components/chip/Chip.js.map +1 -1
- package/dist/components/combobox/Combobox.cjs.map +1 -1
- package/dist/components/combobox/Combobox.js.map +1 -1
- package/dist/components/commandpalette/CommandPalette.cjs.map +1 -1
- package/dist/components/commandpalette/CommandPalette.js.map +1 -1
- package/dist/components/contextmenu/ContextMenu.cjs.map +1 -1
- package/dist/components/contextmenu/ContextMenu.js.map +1 -1
- package/dist/components/datagrid/DataGrid.cjs.map +1 -1
- package/dist/components/datagrid/DataGrid.js.map +1 -1
- package/dist/components/datepicker/DatePicker.cjs.map +1 -1
- package/dist/components/datepicker/DatePicker.js.map +1 -1
- package/dist/components/daterangepicker/DateRangePicker.cjs.map +1 -1
- package/dist/components/daterangepicker/DateRangePicker.js.map +1 -1
- package/dist/components/drawer/Drawer.cjs.map +1 -1
- package/dist/components/drawer/Drawer.js.map +1 -1
- package/dist/components/dropdown/Dropdown.cjs.map +1 -1
- package/dist/components/dropdown/Dropdown.js.map +1 -1
- package/dist/components/fileuploader/FileUploader.cjs.map +1 -1
- package/dist/components/fileuploader/FileUploader.js.map +1 -1
- package/dist/components/hovercard/HoverCard.cjs.map +1 -1
- package/dist/components/hovercard/HoverCard.js.map +1 -1
- package/dist/components/input/Input.cjs.map +1 -1
- package/dist/components/input/Input.js.map +1 -1
- package/dist/components/layout/Container.cjs.map +1 -1
- package/dist/components/layout/Container.js.map +1 -1
- package/dist/components/layout/Flex.cjs.map +1 -1
- package/dist/components/layout/Flex.js.map +1 -1
- package/dist/components/layout/Grid.cjs.map +1 -1
- package/dist/components/layout/Grid.js.map +1 -1
- package/dist/components/layout/HStack.cjs.map +1 -1
- package/dist/components/layout/HStack.js.map +1 -1
- package/dist/components/layout/Stack.cjs.map +1 -1
- package/dist/components/layout/Stack.js.map +1 -1
- package/dist/components/megamenu/MegaMenu.cjs.map +1 -1
- package/dist/components/megamenu/MegaMenu.js.map +1 -1
- package/dist/components/modal/Modal.cjs.map +1 -1
- package/dist/components/modal/Modal.js.map +1 -1
- package/dist/components/pagination/Pagination.cjs.map +1 -1
- package/dist/components/pagination/Pagination.js.map +1 -1
- package/dist/components/popover/Popover.cjs.map +1 -1
- package/dist/components/popover/Popover.js.map +1 -1
- package/dist/components/progress/Progress.cjs.map +1 -1
- package/dist/components/progress/Progress.js.map +1 -1
- package/dist/components/radiogroup/RadioGroup.cjs.map +1 -1
- package/dist/components/radiogroup/RadioGroup.js.map +1 -1
- package/dist/components/rating/Rating.cjs.map +1 -1
- package/dist/components/rating/Rating.js.map +1 -1
- package/dist/components/select/Select.cjs +2 -0
- package/dist/components/select/Select.cjs.map +1 -0
- package/dist/components/select/Select.js +165 -0
- package/dist/components/select/Select.js.map +1 -0
- package/dist/components/skeleton/Skeleton.cjs.map +1 -1
- package/dist/components/skeleton/Skeleton.js.map +1 -1
- package/dist/components/slider/Slider.cjs.map +1 -1
- package/dist/components/slider/Slider.js.map +1 -1
- package/dist/components/spinner/Spinner.cjs.map +1 -1
- package/dist/components/spinner/Spinner.js.map +1 -1
- package/dist/components/stepper/Stepper.cjs.map +1 -1
- package/dist/components/stepper/Stepper.js.map +1 -1
- package/dist/components/switch/Switch.cjs.map +1 -1
- package/dist/components/switch/Switch.js.map +1 -1
- package/dist/components/table/Table.cjs.map +1 -1
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/tabs/Tabs.cjs.map +1 -1
- package/dist/components/tabs/Tabs.js.map +1 -1
- package/dist/components/textarea/Textarea.cjs.map +1 -1
- package/dist/components/textarea/Textarea.js.map +1 -1
- package/dist/components/toast/Toast.cjs.map +1 -1
- package/dist/components/toast/Toast.js.map +1 -1
- package/dist/components/tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/tooltip/Tooltip.js.map +1 -1
- package/dist/components/treeview/TreeView.cjs.map +1 -1
- package/dist/components/treeview/TreeView.js.map +1 -1
- package/dist/components/virtuallist/VirtualList.cjs.map +1 -1
- package/dist/components/virtuallist/VirtualList.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +58 -56
- package/dist/index.js.map +1 -1
- package/dist/package.json +33 -0
- package/dist/styles/{index.css → nui.css} +1 -1
- package/dist/theme/NUIProvider.cjs.map +1 -1
- package/dist/theme/NUIProvider.js.map +1 -1
- package/dist/theme/useTheme.cjs.map +1 -1
- package/dist/theme/useTheme.js.map +1 -1
- package/dist/types/components/accordion/Accordion.d.ts.map +1 -1
- package/dist/types/components/alert/Alert.d.ts.map +1 -1
- package/dist/types/components/avatar/Avatar.d.ts.map +1 -1
- package/dist/types/components/avatar/AvatarGroup.d.ts.map +1 -1
- package/dist/types/components/badge/Badge.d.ts.map +1 -1
- package/dist/types/components/badge/BadgeGroup.d.ts.map +1 -1
- package/dist/types/components/breadcrumbs/Breadcrumbs.d.ts.map +1 -1
- package/dist/types/components/button/Button.d.ts.map +1 -1
- package/dist/types/components/card/Card.d.ts.map +1 -1
- package/dist/types/components/checkbox/Checkbox.d.ts.map +1 -1
- package/dist/types/components/chip/Chip.d.ts.map +1 -1
- package/dist/types/components/combobox/Combobox.d.ts.map +1 -1
- package/dist/types/components/commandpalette/CommandPalette.d.ts.map +1 -1
- package/dist/types/components/contextmenu/ContextMenu.d.ts.map +1 -1
- package/dist/types/components/datagrid/DataGrid.d.ts.map +1 -1
- package/dist/types/components/datepicker/DatePicker.d.ts.map +1 -1
- package/dist/types/components/daterangepicker/DateRangePicker.d.ts.map +1 -1
- package/dist/types/components/drawer/Drawer.d.ts.map +1 -1
- package/dist/types/components/dropdown/Dropdown.d.ts.map +1 -1
- package/dist/types/components/fileuploader/FileUploader.d.ts.map +1 -1
- package/dist/types/components/hovercard/HoverCard.d.ts.map +1 -1
- package/dist/types/components/input/Input.d.ts.map +1 -1
- package/dist/types/components/layout/Container.d.ts.map +1 -1
- package/dist/types/components/layout/Flex.d.ts.map +1 -1
- package/dist/types/components/layout/Grid.d.ts.map +1 -1
- package/dist/types/components/layout/HStack.d.ts.map +1 -1
- package/dist/types/components/layout/Stack.d.ts.map +1 -1
- package/dist/types/components/megamenu/MegaMenu.d.ts.map +1 -1
- package/dist/types/components/modal/Modal.d.ts.map +1 -1
- package/dist/types/components/pagination/Pagination.d.ts.map +1 -1
- package/dist/types/components/popover/Popover.d.ts.map +1 -1
- package/dist/types/components/progress/Progress.d.ts.map +1 -1
- package/dist/types/components/radiogroup/RadioGroup.d.ts.map +1 -1
- package/dist/types/components/rating/Rating.d.ts.map +1 -1
- package/dist/types/components/select/Select.d.ts.map +1 -1
- package/dist/types/components/skeleton/Skeleton.d.ts.map +1 -1
- package/dist/types/components/slider/Slider.d.ts.map +1 -1
- package/dist/types/components/spinner/Spinner.d.ts.map +1 -1
- package/dist/types/components/stepper/Stepper.d.ts.map +1 -1
- package/dist/types/components/switch/Switch.d.ts.map +1 -1
- package/dist/types/components/table/Table.d.ts.map +1 -1
- package/dist/types/components/tabs/Tabs.d.ts.map +1 -1
- package/dist/types/components/textarea/Textarea.d.ts.map +1 -1
- package/dist/types/components/toast/Toast.d.ts.map +1 -1
- package/dist/types/components/tooltip/Tooltip.d.ts.map +1 -1
- package/dist/types/components/treeview/TreeView.d.ts.map +1 -1
- package/dist/types/components/virtuallist/VirtualList.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/theme/NUIProvider.d.ts.map +1 -1
- package/dist/types/theme/useTheme.d.ts.map +1 -1
- package/dist/types/utils/generateid/generateId.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/inertmanager/inertManager.d.ts.map +1 -1
- package/dist/types/utils/keyboardnav/keyboardNav.d.ts.map +1 -1
- package/dist/types/utils/onclickoutside/onClickOutside.d.ts.map +1 -1
- package/dist/types/utils/portal/portal.d.ts.map +1 -1
- package/dist/types/utils/restorefocus/restoreFocus.d.ts.map +1 -1
- package/dist/types/utils/scrolllock/scrollLock.d.ts.map +1 -1
- package/dist/types/utils/trapfocus/trapFocus.d.ts.map +1 -1
- package/dist/utils/generateid/generateId.cjs.map +1 -1
- package/dist/utils/generateid/generateId.js.map +1 -1
- package/dist/utils/inertmanager/inertManager.cjs.map +1 -1
- package/dist/utils/inertmanager/inertManager.js.map +1 -1
- package/dist/utils/keyboardnav/keyboardNav.cjs.map +1 -1
- package/dist/utils/keyboardnav/keyboardNav.js.map +1 -1
- package/dist/utils/onclickoutside/onClickOutside.cjs.map +1 -1
- package/dist/utils/onclickoutside/onClickOutside.js.map +1 -1
- package/dist/utils/portal/portal.cjs.map +1 -1
- package/dist/utils/portal/portal.js.map +1 -1
- package/dist/utils/restorefocus/restoreFocus.cjs.map +1 -1
- package/dist/utils/restorefocus/restoreFocus.js.map +1 -1
- package/dist/utils/scrolllock/scrollLock.cjs.map +1 -1
- package/dist/utils/scrolllock/scrollLock.js.map +1 -1
- package/dist/utils/trapfocus/trapFocus.cjs.map +1 -1
- package/dist/utils/trapfocus/trapFocus.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.cjs","sources":["
|
|
1
|
+
{"version":3,"file":"Popover.cjs","sources":["../../../src/components/popover/Popover.tsx"],"sourcesContent":["import React, {\r\n createContext,\r\n useContext,\r\n useRef,\r\n useEffect,\r\n useState,\r\n useLayoutEffect,\r\n useId,\r\n} from 'react';\r\n\r\nimport './Popover.css';\r\n\r\n// Import your existing utilities\r\n// Ensure these paths match your folder structure exactly!\r\nimport { Portal, onClickOutside, restoreFocus, trapFocus } from '../../utils/index'; \r\n\r\ntype Placement = 'top' | 'bottom' | 'left' | 'right';\r\n\r\nconst PopoverContext = createContext<{\r\n open: boolean;\r\n setOpen: React.Dispatch<React.SetStateAction<boolean>>;\r\n triggerRef: React.RefObject<HTMLElement | null>;\r\n contentRef: React.RefObject<HTMLDivElement | null>;\r\n contentId: string;\r\n} | null>(null);\r\n\r\nfunction usePopover() {\r\n const ctx = useContext(PopoverContext);\r\n if (!ctx) throw new Error('Popover components must be inside <Popover>');\r\n return ctx;\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Root\r\n * ------------------------------------------------------*/\r\nexport function Popover({ children }: { children: React.ReactNode }) {\r\n const [open, setOpen] = useState(false);\r\n const triggerRef = useRef<HTMLElement | null>(null);\r\n const contentRef = useRef<HTMLDivElement | null>(null);\r\n const contentId = `popover-${useId()}`;\r\n\r\n // Restore focus to trigger on close\r\n useEffect(() => {\r\n if (!open) {\r\n restoreFocus(triggerRef.current);\r\n }\r\n }, [open]);\r\n\r\n return (\r\n <PopoverContext.Provider\r\n value={{ open, setOpen, triggerRef, contentRef, contentId }}\r\n >\r\n {/* Wrapper ensures trigger stays in document flow */}\r\n <div className=\"ui-popover-root\">{children}</div>\r\n </PopoverContext.Provider>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Trigger\r\n * ------------------------------------------------------*/\r\nexport function PopoverTrigger({\r\n children,\r\n className = '',\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { open, setOpen, triggerRef, contentId } = usePopover();\r\n\r\n return (\r\n <button\r\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\r\n type=\"button\"\r\n className={`ui-popover-trigger ${className}`}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n aria-controls={open ? contentId : undefined}\r\n onClick={(e) => {\r\n // Stop propagation to prevent immediate close by external listeners\r\n e.stopPropagation();\r\n setOpen((s) => !s);\r\n }}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && open) {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n }}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Content (Portaled & Positioned)\r\n * ------------------------------------------------------*/\r\nexport function PopoverContent({\r\n children,\r\n className = '',\r\n placement = 'bottom',\r\n offset = 8,\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n placement?: Placement;\r\n offset?: number;\r\n}) {\r\n const { open, setOpen, triggerRef, contentRef, contentId } = usePopover();\r\n const [coords, setCoords] = useState<{ top: number; left: number } | null>(null);\r\n\r\n // 1. Trap Focus when open (FIXED RETURN TYPE)\r\n useEffect(() => {\r\n if (open && contentRef.current) {\r\n // Return the cleanup function from trapFocus\r\n return trapFocus(contentRef.current);\r\n }\r\n // Explicitly return undefined so TS knows we handled the \"else\" case\r\n return undefined;\r\n }, [open, contentRef]);\r\n\r\n // 2. Click Outside Logic\r\n useEffect(() => {\r\n if (!open) return;\r\n\r\n const cleanup = onClickOutside(contentRef, () => setOpen(false));\r\n return cleanup;\r\n }, [open, setOpen, contentRef, triggerRef]);\r\n\r\n // 3. Global Escape Key\r\n useEffect(() => {\r\n if (!open) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [open, setOpen]);\r\n\r\n // 4. Calculate Position\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) return;\r\n\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const scrollX = window.scrollX;\r\n const scrollY = window.scrollY;\r\n\r\n let top = 0;\r\n let left = 0;\r\n\r\n switch (placement) {\r\n case 'bottom':\r\n top = rect.bottom + offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'top':\r\n top = rect.top - offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'left':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.left - offset + scrollX;\r\n break;\r\n case 'right':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.right + offset + scrollX;\r\n break;\r\n }\r\n\r\n setCoords({ top, left });\r\n }, [open, placement, offset, triggerRef]);\r\n\r\n if (!open) return null;\r\n\r\n return (\r\n <Portal>\r\n <div\r\n id={contentId}\r\n ref={contentRef}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n className={`ui-popover-content ui-popover-${placement} ${className}`}\r\n style={{\r\n position: 'absolute',\r\n top: coords?.top ?? 0,\r\n left: coords?.left ?? 0,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n </Portal>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Close Button\r\n * ------------------------------------------------------*/\r\nexport function PopoverClose({\r\n children = 'Close',\r\n className = '',\r\n}: {\r\n children?: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { setOpen } = usePopover();\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen(false)}\r\n className={`ui-popover-close ${className}`}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}"],"names":["PopoverContext","createContext","usePopover","ctx","useContext","Popover","children","open","setOpen","useState","triggerRef","useRef","contentRef","contentId","useId","useEffect","restoreFocus","jsx","PopoverTrigger","className","e","s","PopoverContent","placement","offset","coords","setCoords","trapFocus","onClickOutside","handler","useLayoutEffect","rect","scrollX","scrollY","top","left","Portal","PopoverClose"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.js","sources":["../../../../src/components/popover/Popover.tsx"],"sourcesContent":["import React, {\r\n createContext,\r\n useContext,\r\n useRef,\r\n useEffect,\r\n useState,\r\n useLayoutEffect,\r\n useId,\r\n} from 'react';\r\n\r\nimport './Popover.css';\r\n\r\n// Import your existing utilities\r\n// Ensure these paths match your folder structure exactly!\r\nimport { Portal, onClickOutside, restoreFocus, trapFocus } from '../../utils/index'; \r\n\r\ntype Placement = 'top' | 'bottom' | 'left' | 'right';\r\n\r\nconst PopoverContext = createContext<{\r\n open: boolean;\r\n setOpen: React.Dispatch<React.SetStateAction<boolean>>;\r\n triggerRef: React.RefObject<HTMLElement | null>;\r\n contentRef: React.RefObject<HTMLDivElement | null>;\r\n contentId: string;\r\n} | null>(null);\r\n\r\nfunction usePopover() {\r\n const ctx = useContext(PopoverContext);\r\n if (!ctx) throw new Error('Popover components must be inside <Popover>');\r\n return ctx;\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Root\r\n * ------------------------------------------------------*/\r\nexport function Popover({ children }: { children: React.ReactNode }) {\r\n const [open, setOpen] = useState(false);\r\n const triggerRef = useRef<HTMLElement | null>(null);\r\n const contentRef = useRef<HTMLDivElement | null>(null);\r\n const contentId = `popover-${useId()}`;\r\n\r\n // Restore focus to trigger on close\r\n useEffect(() => {\r\n if (!open) {\r\n restoreFocus(triggerRef.current);\r\n }\r\n }, [open]);\r\n\r\n return (\r\n <PopoverContext.Provider\r\n value={{ open, setOpen, triggerRef, contentRef, contentId }}\r\n >\r\n {/* Wrapper ensures trigger stays in document flow */}\r\n <div className=\"ui-popover-root\">{children}</div>\r\n </PopoverContext.Provider>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Trigger\r\n * ------------------------------------------------------*/\r\nexport function PopoverTrigger({\r\n children,\r\n className = '',\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { open, setOpen, triggerRef, contentId } = usePopover();\r\n\r\n return (\r\n <button\r\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\r\n type=\"button\"\r\n className={`ui-popover-trigger ${className}`}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n aria-controls={open ? contentId : undefined}\r\n onClick={(e) => {\r\n // Stop propagation to prevent immediate close by external listeners\r\n e.stopPropagation();\r\n setOpen((s) => !s);\r\n }}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && open) {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n }}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Content (Portaled & Positioned)\r\n * ------------------------------------------------------*/\r\nexport function PopoverContent({\r\n children,\r\n className = '',\r\n placement = 'bottom',\r\n offset = 8,\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n placement?: Placement;\r\n offset?: number;\r\n}) {\r\n const { open, setOpen, triggerRef, contentRef, contentId } = usePopover();\r\n const [coords, setCoords] = useState<{ top: number; left: number } | null>(null);\r\n\r\n // 1. Trap Focus when open (FIXED RETURN TYPE)\r\n useEffect(() => {\r\n if (open && contentRef.current) {\r\n // Return the cleanup function from trapFocus\r\n return trapFocus(contentRef.current);\r\n }\r\n // Explicitly return undefined so TS knows we handled the \"else\" case\r\n return undefined;\r\n }, [open, contentRef]);\r\n\r\n // 2. Click Outside Logic\r\n useEffect(() => {\r\n if (!open) return;\r\n\r\n const cleanup = onClickOutside(contentRef, () => setOpen(false));\r\n return cleanup;\r\n }, [open, setOpen, contentRef, triggerRef]);\r\n\r\n // 3. Global Escape Key\r\n useEffect(() => {\r\n if (!open) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [open, setOpen]);\r\n\r\n // 4. Calculate Position\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) return;\r\n\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const scrollX = window.scrollX;\r\n const scrollY = window.scrollY;\r\n\r\n let top = 0;\r\n let left = 0;\r\n\r\n switch (placement) {\r\n case 'bottom':\r\n top = rect.bottom + offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'top':\r\n top = rect.top - offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'left':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.left - offset + scrollX;\r\n break;\r\n case 'right':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.right + offset + scrollX;\r\n break;\r\n }\r\n\r\n setCoords({ top, left });\r\n }, [open, placement, offset, triggerRef]);\r\n\r\n if (!open) return null;\r\n\r\n return (\r\n <Portal>\r\n <div\r\n id={contentId}\r\n ref={contentRef}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n className={`ui-popover-content ui-popover-${placement} ${className}`}\r\n style={{\r\n position: 'absolute',\r\n top: coords?.top ?? 0,\r\n left: coords?.left ?? 0,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n </Portal>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Close Button\r\n * ------------------------------------------------------*/\r\nexport function PopoverClose({\r\n children = 'Close',\r\n className = '',\r\n}: {\r\n children?: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { setOpen } = usePopover();\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen(false)}\r\n className={`ui-popover-close ${className}`}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}"],"names":["PopoverContext","createContext","usePopover","ctx","useContext","Popover","children","open","setOpen","useState","triggerRef","useRef","contentRef","contentId","useId","useEffect","restoreFocus","jsx","PopoverTrigger","className","e","s","PopoverContent","placement","offset","coords","setCoords","trapFocus","onClickOutside","handler","useLayoutEffect","rect","scrollX","scrollY","top","left","Portal","PopoverClose"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Popover.js","sources":["../../../src/components/popover/Popover.tsx"],"sourcesContent":["import React, {\r\n createContext,\r\n useContext,\r\n useRef,\r\n useEffect,\r\n useState,\r\n useLayoutEffect,\r\n useId,\r\n} from 'react';\r\n\r\nimport './Popover.css';\r\n\r\n// Import your existing utilities\r\n// Ensure these paths match your folder structure exactly!\r\nimport { Portal, onClickOutside, restoreFocus, trapFocus } from '../../utils/index'; \r\n\r\ntype Placement = 'top' | 'bottom' | 'left' | 'right';\r\n\r\nconst PopoverContext = createContext<{\r\n open: boolean;\r\n setOpen: React.Dispatch<React.SetStateAction<boolean>>;\r\n triggerRef: React.RefObject<HTMLElement | null>;\r\n contentRef: React.RefObject<HTMLDivElement | null>;\r\n contentId: string;\r\n} | null>(null);\r\n\r\nfunction usePopover() {\r\n const ctx = useContext(PopoverContext);\r\n if (!ctx) throw new Error('Popover components must be inside <Popover>');\r\n return ctx;\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Root\r\n * ------------------------------------------------------*/\r\nexport function Popover({ children }: { children: React.ReactNode }) {\r\n const [open, setOpen] = useState(false);\r\n const triggerRef = useRef<HTMLElement | null>(null);\r\n const contentRef = useRef<HTMLDivElement | null>(null);\r\n const contentId = `popover-${useId()}`;\r\n\r\n // Restore focus to trigger on close\r\n useEffect(() => {\r\n if (!open) {\r\n restoreFocus(triggerRef.current);\r\n }\r\n }, [open]);\r\n\r\n return (\r\n <PopoverContext.Provider\r\n value={{ open, setOpen, triggerRef, contentRef, contentId }}\r\n >\r\n {/* Wrapper ensures trigger stays in document flow */}\r\n <div className=\"ui-popover-root\">{children}</div>\r\n </PopoverContext.Provider>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Trigger\r\n * ------------------------------------------------------*/\r\nexport function PopoverTrigger({\r\n children,\r\n className = '',\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { open, setOpen, triggerRef, contentId } = usePopover();\r\n\r\n return (\r\n <button\r\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\r\n type=\"button\"\r\n className={`ui-popover-trigger ${className}`}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n aria-controls={open ? contentId : undefined}\r\n onClick={(e) => {\r\n // Stop propagation to prevent immediate close by external listeners\r\n e.stopPropagation();\r\n setOpen((s) => !s);\r\n }}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && open) {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n }}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Content (Portaled & Positioned)\r\n * ------------------------------------------------------*/\r\nexport function PopoverContent({\r\n children,\r\n className = '',\r\n placement = 'bottom',\r\n offset = 8,\r\n}: {\r\n children: React.ReactNode;\r\n className?: string;\r\n placement?: Placement;\r\n offset?: number;\r\n}) {\r\n const { open, setOpen, triggerRef, contentRef, contentId } = usePopover();\r\n const [coords, setCoords] = useState<{ top: number; left: number } | null>(null);\r\n\r\n // 1. Trap Focus when open (FIXED RETURN TYPE)\r\n useEffect(() => {\r\n if (open && contentRef.current) {\r\n // Return the cleanup function from trapFocus\r\n return trapFocus(contentRef.current);\r\n }\r\n // Explicitly return undefined so TS knows we handled the \"else\" case\r\n return undefined;\r\n }, [open, contentRef]);\r\n\r\n // 2. Click Outside Logic\r\n useEffect(() => {\r\n if (!open) return;\r\n\r\n const cleanup = onClickOutside(contentRef, () => setOpen(false));\r\n return cleanup;\r\n }, [open, setOpen, contentRef, triggerRef]);\r\n\r\n // 3. Global Escape Key\r\n useEffect(() => {\r\n if (!open) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n e.stopPropagation();\r\n setOpen(false);\r\n }\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [open, setOpen]);\r\n\r\n // 4. Calculate Position\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) return;\r\n\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const scrollX = window.scrollX;\r\n const scrollY = window.scrollY;\r\n\r\n let top = 0;\r\n let left = 0;\r\n\r\n switch (placement) {\r\n case 'bottom':\r\n top = rect.bottom + offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'top':\r\n top = rect.top - offset + scrollY;\r\n left = rect.left + (rect.width / 2) + scrollX;\r\n break;\r\n case 'left':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.left - offset + scrollX;\r\n break;\r\n case 'right':\r\n top = rect.top + (rect.height / 2) + scrollY;\r\n left = rect.right + offset + scrollX;\r\n break;\r\n }\r\n\r\n setCoords({ top, left });\r\n }, [open, placement, offset, triggerRef]);\r\n\r\n if (!open) return null;\r\n\r\n return (\r\n <Portal>\r\n <div\r\n id={contentId}\r\n ref={contentRef}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n className={`ui-popover-content ui-popover-${placement} ${className}`}\r\n style={{\r\n position: 'absolute',\r\n top: coords?.top ?? 0,\r\n left: coords?.left ?? 0,\r\n }}\r\n >\r\n {children}\r\n </div>\r\n </Portal>\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Close Button\r\n * ------------------------------------------------------*/\r\nexport function PopoverClose({\r\n children = 'Close',\r\n className = '',\r\n}: {\r\n children?: React.ReactNode;\r\n className?: string;\r\n}) {\r\n const { setOpen } = usePopover();\r\n return (\r\n <button\r\n type=\"button\"\r\n onClick={() => setOpen(false)}\r\n className={`ui-popover-close ${className}`}\r\n >\r\n {children}\r\n </button>\r\n );\r\n}"],"names":["PopoverContext","createContext","usePopover","ctx","useContext","Popover","children","open","setOpen","useState","triggerRef","useRef","contentRef","contentId","useId","useEffect","restoreFocus","jsx","PopoverTrigger","className","e","s","PopoverContent","placement","offset","coords","setCoords","trapFocus","onClickOutside","handler","useLayoutEffect","rect","scrollX","scrollY","top","left","Portal","PopoverClose"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Progress.cjs","sources":["
|
|
1
|
+
{"version":3,"file":"Progress.cjs","sources":["../../../src/components/progress/Progress.tsx"],"sourcesContent":["/**\r\n * Progress.tsx — FINAL VERSION\r\n * --------------------------------\r\n * Features:\r\n * - Determinate mode (value/max)\r\n * - Indeterminate mode\r\n * - Smooth animation\r\n * - ARIA compliant\r\n * - Pure TSX + CSS (no inline styles except width)\r\n */\r\n\r\nimport './Progress.css';\r\n\r\nexport interface ProgressProps {\r\n /** Current value (determinate mode). */\r\n value?: number;\r\n\r\n /** Maximum value (default: 100). */\r\n max?: number;\r\n\r\n /** Indeterminate mode when value is undefined. */\r\n indeterminate?: boolean;\r\n\r\n /** Optional label for screen readers. */\r\n label?: string;\r\n\r\n className?: string;\r\n}\r\n\r\nexport function Progress({\r\n value,\r\n max = 100,\r\n indeterminate = false,\r\n label = 'Progress',\r\n className = '',\r\n}: ProgressProps) {\r\n const isIndeterminate = indeterminate || value === undefined;\r\n\r\n const percentage = Math.min(Math.max((value ?? 0) / max, 0), 1) * 100;\r\n\r\n return (\r\n <div\r\n className={`ui-progress ${className}`}\r\n role=\"progressbar\"\r\n aria-valuemin={isIndeterminate ? undefined : 0}\r\n aria-valuemax={isIndeterminate ? undefined : max}\r\n aria-valuenow={isIndeterminate ? undefined : value}\r\n aria-label={label}\r\n >\r\n <div\r\n className={`ui-progress-bar ${isIndeterminate ? 'indeterminate' : ''}`}\r\n style={!isIndeterminate ? { width: `${percentage}%` } : undefined}\r\n />\r\n </div>\r\n );\r\n}\r\n"],"names":["Progress","value","max","indeterminate","label","className","isIndeterminate","percentage","jsx"],"mappings":"mJA6BO,SAASA,EAAS,CACvB,MAAAC,EACA,IAAAC,EAAM,IACN,cAAAC,EAAgB,GAChB,MAAAC,EAAQ,WACR,UAAAC,EAAY,EACd,EAAkB,CAChB,MAAMC,EAAkBH,GAAiBF,IAAU,OAE7CM,EAAa,KAAK,IAAI,KAAK,KAAKN,GAAS,GAAKC,EAAK,CAAC,EAAG,CAAC,EAAI,IAElE,OACEM,EAAAA,IAAC,MAAA,CACC,UAAW,eAAeH,CAAS,GACnC,KAAK,cACL,gBAAeC,EAAkB,OAAY,EAC7C,gBAAeA,EAAkB,OAAYJ,EAC7C,gBAAeI,EAAkB,OAAYL,EAC7C,aAAYG,EAEZ,SAAAI,EAAAA,IAAC,MAAA,CACC,UAAW,mBAAmBF,EAAkB,gBAAkB,EAAE,GACpE,MAAQA,EAAgD,OAA9B,CAAE,MAAO,GAAGC,CAAU,IAAQ,CAAA,CAC1D,CAAA,CAGN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Progress.js","sources":["
|
|
1
|
+
{"version":3,"file":"Progress.js","sources":["../../../src/components/progress/Progress.tsx"],"sourcesContent":["/**\r\n * Progress.tsx — FINAL VERSION\r\n * --------------------------------\r\n * Features:\r\n * - Determinate mode (value/max)\r\n * - Indeterminate mode\r\n * - Smooth animation\r\n * - ARIA compliant\r\n * - Pure TSX + CSS (no inline styles except width)\r\n */\r\n\r\nimport './Progress.css';\r\n\r\nexport interface ProgressProps {\r\n /** Current value (determinate mode). */\r\n value?: number;\r\n\r\n /** Maximum value (default: 100). */\r\n max?: number;\r\n\r\n /** Indeterminate mode when value is undefined. */\r\n indeterminate?: boolean;\r\n\r\n /** Optional label for screen readers. */\r\n label?: string;\r\n\r\n className?: string;\r\n}\r\n\r\nexport function Progress({\r\n value,\r\n max = 100,\r\n indeterminate = false,\r\n label = 'Progress',\r\n className = '',\r\n}: ProgressProps) {\r\n const isIndeterminate = indeterminate || value === undefined;\r\n\r\n const percentage = Math.min(Math.max((value ?? 0) / max, 0), 1) * 100;\r\n\r\n return (\r\n <div\r\n className={`ui-progress ${className}`}\r\n role=\"progressbar\"\r\n aria-valuemin={isIndeterminate ? undefined : 0}\r\n aria-valuemax={isIndeterminate ? undefined : max}\r\n aria-valuenow={isIndeterminate ? undefined : value}\r\n aria-label={label}\r\n >\r\n <div\r\n className={`ui-progress-bar ${isIndeterminate ? 'indeterminate' : ''}`}\r\n style={!isIndeterminate ? { width: `${percentage}%` } : undefined}\r\n />\r\n </div>\r\n );\r\n}\r\n"],"names":["Progress","value","max","indeterminate","label","className","isIndeterminate","percentage","jsx"],"mappings":";;AA6BO,SAASA,EAAS;AAAA,EACvB,OAAAC;AAAA,EACA,KAAAC,IAAM;AAAA,EACN,eAAAC,IAAgB;AAAA,EAChB,OAAAC,IAAQ;AAAA,EACR,WAAAC,IAAY;AACd,GAAkB;AAChB,QAAMC,IAAkBH,KAAiBF,MAAU,QAE7CM,IAAa,KAAK,IAAI,KAAK,KAAKN,KAAS,KAAKC,GAAK,CAAC,GAAG,CAAC,IAAI;AAElE,SACE,gBAAAM;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,eAAeH,CAAS;AAAA,MACnC,MAAK;AAAA,MACL,iBAAeC,IAAkB,SAAY;AAAA,MAC7C,iBAAeA,IAAkB,SAAYJ;AAAA,MAC7C,iBAAeI,IAAkB,SAAYL;AAAA,MAC7C,cAAYG;AAAA,MAEZ,UAAA,gBAAAI;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,WAAW,mBAAmBF,IAAkB,kBAAkB,EAAE;AAAA,UACpE,OAAQA,IAAgD,SAA9B,EAAE,OAAO,GAAGC,CAAU;QAAQ;AAAA,MAAA;AAAA,IAC1D;AAAA,EAAA;AAGN;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RadioGroup.cjs","sources":["
|
|
1
|
+
{"version":3,"file":"RadioGroup.cjs","sources":["../../../src/components/radiogroup/RadioGroup.tsx"],"sourcesContent":["/**\r\n * RadioGroup.tsx\r\n * ----------------\r\n * Fully accessible WAI-ARIA radio group.\r\n * - Arrow key navigation\r\n * - Home/End keys\r\n * - Controlled & uncontrolled\r\n * - Disabled support\r\n */\r\n\r\nimport React, { useState, useCallback, useRef } from 'react';\r\nimport './RadioGroup.css';\r\n\r\nexport interface RadioOption {\r\n label: React.ReactNode;\r\n value: string;\r\n disabled?: boolean;\r\n}\r\n\r\ninterface RadioGroupProps {\r\n options: RadioOption[];\r\n value?: string; // controlled\r\n defaultValue?: string; // uncontrolled\r\n onChange?: (value: string) => void;\r\n name?: string; // form support\r\n disabled?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function RadioGroup({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n name,\r\n disabled = false,\r\n className = '',\r\n}: RadioGroupProps) {\r\n const isControlled = value !== undefined;\r\n\r\n const [internalValue, setInternalValue] = useState(defaultValue);\r\n const selected = isControlled ? value : internalValue;\r\n\r\n const itemRefs = useRef<Array<HTMLInputElement | null>>([]);\r\n\r\n const select = useCallback(\r\n (val: string) => {\r\n if (!isControlled) setInternalValue(val);\r\n onChange?.(val);\r\n },\r\n [isControlled, onChange]\r\n );\r\n\r\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\r\n const enabledIndices = options\r\n .map((o, i) => (!o.disabled ? i : null))\r\n .filter((i) => i !== null) as number[];\r\n\r\n const currentIndex = enabledIndices.indexOf(index);\r\n if (currentIndex === -1) return;\r\n\r\n let nextIndex = currentIndex;\r\n\r\n switch (e.key) {\r\n case 'ArrowDown':\r\n case 'ArrowRight':\r\n nextIndex = (currentIndex + 1) % enabledIndices.length;\r\n break;\r\n\r\n case 'ArrowUp':\r\n case 'ArrowLeft':\r\n nextIndex =\r\n (currentIndex - 1 + enabledIndices.length) % enabledIndices.length;\r\n break;\r\n\r\n case 'Home':\r\n nextIndex = 0;\r\n break;\r\n\r\n case 'End':\r\n nextIndex = enabledIndices.length - 1;\r\n break;\r\n\r\n default:\r\n return;\r\n }\r\n\r\n e.preventDefault();\r\n const targetIndex = enabledIndices[nextIndex];\r\n const target = itemRefs.current[targetIndex];\r\n target?.focus();\r\n select(options[targetIndex].value);\r\n };\r\n\r\n return (\r\n <div className={`ui-radio-group ${className}`} role=\"radiogroup\">\r\n {options.map((opt, i) => {\r\n const isChecked = selected === opt.value;\r\n\r\n return (\r\n <label\r\n key={opt.value}\r\n className=\"ui-radio-item\"\r\n aria-disabled={disabled || opt.disabled}\r\n >\r\n <input\r\n ref={(el) => {\r\n itemRefs.current[i] = el;\r\n }}\r\n type=\"radio\"\r\n className=\"ui-radio-input\"\r\n name={name}\r\n value={opt.value}\r\n disabled={disabled || opt.disabled}\r\n checked={isChecked}\r\n onChange={() => select(opt.value)}\r\n onKeyDown={(e) => onKeyDown(e, i)}\r\n data-state={isChecked ? 'checked' : 'unchecked'}\r\n />\r\n\r\n <span className=\"ui-radio-label\">{opt.label}</span>\r\n </label>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["RadioGroup","options","value","defaultValue","onChange","name","disabled","className","isControlled","internalValue","setInternalValue","useState","selected","itemRefs","useRef","select","useCallback","val","onKeyDown","index","enabledIndices","o","i","currentIndex","nextIndex","targetIndex","jsx","opt","isChecked","jsxs","el","e"],"mappings":"wKA6BO,SAASA,EAAW,CACzB,QAAAC,EACA,MAAAC,EACA,aAAAC,EACA,SAAAC,EACA,KAAAC,EACA,SAAAC,EAAW,GACX,UAAAC,EAAY,EACd,EAAoB,CAClB,MAAMC,EAAeN,IAAU,OAEzB,CAACO,EAAeC,CAAgB,EAAIC,EAAAA,SAASR,CAAY,EACzDS,EAAWJ,EAAeN,EAAQO,EAElCI,EAAWC,EAAAA,OAAuC,EAAE,EAEpDC,EAASC,EAAAA,YACZC,GAAgB,CACVT,GAAcE,EAAiBO,CAAG,EACvCb,IAAWa,CAAG,CAChB,EACA,CAACT,EAAcJ,CAAQ,CAAA,EAGnBc,EAAY,CAAC,EAAwBC,IAAkB,CAC3D,MAAMC,EAAiBnB,EACpB,IAAI,CAACoB,EAAGC,IAAQD,EAAE,SAAe,KAAJC,CAAS,EACtC,OAAQ,GAAM,IAAM,IAAI,EAErBC,EAAeH,EAAe,QAAQD,CAAK,EACjD,GAAII,IAAiB,GAAI,OAEzB,IAAIC,EAAYD,EAEhB,OAAQ,EAAE,IAAA,CACR,IAAK,YACL,IAAK,aACHC,GAAaD,EAAe,GAAKH,EAAe,OAChD,MAEF,IAAK,UACL,IAAK,YACHI,GACGD,EAAe,EAAIH,EAAe,QAAUA,EAAe,OAC9D,MAEF,IAAK,OACHI,EAAY,EACZ,MAEF,IAAK,MACHA,EAAYJ,EAAe,OAAS,EACpC,MAEF,QACE,MAAA,CAGJ,EAAE,eAAA,EACF,MAAMK,EAAcL,EAAeI,CAAS,EAC7BX,EAAS,QAAQY,CAAW,GACnC,MAAA,EACRV,EAAOd,EAAQwB,CAAW,EAAE,KAAK,CACnC,EAEA,OACEC,EAAAA,IAAC,MAAA,CAAI,UAAW,kBAAkBnB,CAAS,GAAI,KAAK,aACjD,SAAAN,EAAQ,IAAI,CAAC0B,EAAKL,IAAM,CACvB,MAAMM,EAAYhB,IAAae,EAAI,MAEnC,OACEE,EAAAA,KAAC,QAAA,CAEC,UAAU,gBACV,gBAAevB,GAAYqB,EAAI,SAE/B,SAAA,CAAAD,EAAAA,IAAC,QAAA,CACC,IAAMI,GAAO,CACXjB,EAAS,QAAQS,CAAC,EAAIQ,CACxB,EACA,KAAK,QACL,UAAU,iBACV,KAAAzB,EACA,MAAOsB,EAAI,MACX,SAAUrB,GAAYqB,EAAI,SAC1B,QAASC,EACT,SAAU,IAAMb,EAAOY,EAAI,KAAK,EAChC,UAAYI,GAAMb,EAAUa,EAAGT,CAAC,EAChC,aAAYM,EAAY,UAAY,WAAA,CAAA,EAGtCF,EAAAA,IAAC,OAAA,CAAK,UAAU,iBAAkB,WAAI,KAAA,CAAM,CAAA,CAAA,EAnBvCC,EAAI,KAAA,CAsBf,CAAC,CAAA,CACH,CAEJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RadioGroup.js","sources":["
|
|
1
|
+
{"version":3,"file":"RadioGroup.js","sources":["../../../src/components/radiogroup/RadioGroup.tsx"],"sourcesContent":["/**\r\n * RadioGroup.tsx\r\n * ----------------\r\n * Fully accessible WAI-ARIA radio group.\r\n * - Arrow key navigation\r\n * - Home/End keys\r\n * - Controlled & uncontrolled\r\n * - Disabled support\r\n */\r\n\r\nimport React, { useState, useCallback, useRef } from 'react';\r\nimport './RadioGroup.css';\r\n\r\nexport interface RadioOption {\r\n label: React.ReactNode;\r\n value: string;\r\n disabled?: boolean;\r\n}\r\n\r\ninterface RadioGroupProps {\r\n options: RadioOption[];\r\n value?: string; // controlled\r\n defaultValue?: string; // uncontrolled\r\n onChange?: (value: string) => void;\r\n name?: string; // form support\r\n disabled?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function RadioGroup({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n name,\r\n disabled = false,\r\n className = '',\r\n}: RadioGroupProps) {\r\n const isControlled = value !== undefined;\r\n\r\n const [internalValue, setInternalValue] = useState(defaultValue);\r\n const selected = isControlled ? value : internalValue;\r\n\r\n const itemRefs = useRef<Array<HTMLInputElement | null>>([]);\r\n\r\n const select = useCallback(\r\n (val: string) => {\r\n if (!isControlled) setInternalValue(val);\r\n onChange?.(val);\r\n },\r\n [isControlled, onChange]\r\n );\r\n\r\n const onKeyDown = (e: React.KeyboardEvent, index: number) => {\r\n const enabledIndices = options\r\n .map((o, i) => (!o.disabled ? i : null))\r\n .filter((i) => i !== null) as number[];\r\n\r\n const currentIndex = enabledIndices.indexOf(index);\r\n if (currentIndex === -1) return;\r\n\r\n let nextIndex = currentIndex;\r\n\r\n switch (e.key) {\r\n case 'ArrowDown':\r\n case 'ArrowRight':\r\n nextIndex = (currentIndex + 1) % enabledIndices.length;\r\n break;\r\n\r\n case 'ArrowUp':\r\n case 'ArrowLeft':\r\n nextIndex =\r\n (currentIndex - 1 + enabledIndices.length) % enabledIndices.length;\r\n break;\r\n\r\n case 'Home':\r\n nextIndex = 0;\r\n break;\r\n\r\n case 'End':\r\n nextIndex = enabledIndices.length - 1;\r\n break;\r\n\r\n default:\r\n return;\r\n }\r\n\r\n e.preventDefault();\r\n const targetIndex = enabledIndices[nextIndex];\r\n const target = itemRefs.current[targetIndex];\r\n target?.focus();\r\n select(options[targetIndex].value);\r\n };\r\n\r\n return (\r\n <div className={`ui-radio-group ${className}`} role=\"radiogroup\">\r\n {options.map((opt, i) => {\r\n const isChecked = selected === opt.value;\r\n\r\n return (\r\n <label\r\n key={opt.value}\r\n className=\"ui-radio-item\"\r\n aria-disabled={disabled || opt.disabled}\r\n >\r\n <input\r\n ref={(el) => {\r\n itemRefs.current[i] = el;\r\n }}\r\n type=\"radio\"\r\n className=\"ui-radio-input\"\r\n name={name}\r\n value={opt.value}\r\n disabled={disabled || opt.disabled}\r\n checked={isChecked}\r\n onChange={() => select(opt.value)}\r\n onKeyDown={(e) => onKeyDown(e, i)}\r\n data-state={isChecked ? 'checked' : 'unchecked'}\r\n />\r\n\r\n <span className=\"ui-radio-label\">{opt.label}</span>\r\n </label>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["RadioGroup","options","value","defaultValue","onChange","name","disabled","className","isControlled","internalValue","setInternalValue","useState","selected","itemRefs","useRef","select","useCallback","val","onKeyDown","index","enabledIndices","o","i","currentIndex","nextIndex","targetIndex","jsx","opt","isChecked","jsxs","el","e"],"mappings":";;;AA6BO,SAASA,EAAW;AAAA,EACzB,SAAAC;AAAA,EACA,OAAAC;AAAA,EACA,cAAAC;AAAA,EACA,UAAAC;AAAA,EACA,MAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,WAAAC,IAAY;AACd,GAAoB;AAClB,QAAMC,IAAeN,MAAU,QAEzB,CAACO,GAAeC,CAAgB,IAAIC,EAASR,CAAY,GACzDS,IAAWJ,IAAeN,IAAQO,GAElCI,IAAWC,EAAuC,EAAE,GAEpDC,IAASC;AAAA,IACb,CAACC,MAAgB;AACf,MAAKT,KAAcE,EAAiBO,CAAG,GACvCb,IAAWa,CAAG;AAAA,IAChB;AAAA,IACA,CAACT,GAAcJ,CAAQ;AAAA,EAAA,GAGnBc,IAAY,CAAC,GAAwBC,MAAkB;AAC3D,UAAMC,IAAiBnB,EACpB,IAAI,CAACoB,GAAGC,MAAQD,EAAE,WAAe,OAAJC,CAAS,EACtC,OAAO,CAACA,MAAMA,MAAM,IAAI,GAErBC,IAAeH,EAAe,QAAQD,CAAK;AACjD,QAAII,MAAiB,GAAI;AAEzB,QAAIC,IAAYD;AAEhB,YAAQ,EAAE,KAAA;AAAA,MACR,KAAK;AAAA,MACL,KAAK;AACH,QAAAC,KAAaD,IAAe,KAAKH,EAAe;AAChD;AAAA,MAEF,KAAK;AAAA,MACL,KAAK;AACH,QAAAI,KACGD,IAAe,IAAIH,EAAe,UAAUA,EAAe;AAC9D;AAAA,MAEF,KAAK;AACH,QAAAI,IAAY;AACZ;AAAA,MAEF,KAAK;AACH,QAAAA,IAAYJ,EAAe,SAAS;AACpC;AAAA,MAEF;AACE;AAAA,IAAA;AAGJ,MAAE,eAAA;AACF,UAAMK,IAAcL,EAAeI,CAAS;AAE5C,IADeX,EAAS,QAAQY,CAAW,GACnC,MAAA,GACRV,EAAOd,EAAQwB,CAAW,EAAE,KAAK;AAAA,EACnC;AAEA,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAW,kBAAkBnB,CAAS,IAAI,MAAK,cACjD,UAAAN,EAAQ,IAAI,CAAC0B,GAAKL,MAAM;AACvB,UAAMM,IAAYhB,MAAae,EAAI;AAEnC,WACE,gBAAAE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QACV,iBAAevB,KAAYqB,EAAI;AAAA,QAE/B,UAAA;AAAA,UAAA,gBAAAD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAK,CAACI,MAAO;AACX,gBAAAjB,EAAS,QAAQS,CAAC,IAAIQ;AAAA,cACxB;AAAA,cACA,MAAK;AAAA,cACL,WAAU;AAAA,cACV,MAAAzB;AAAA,cACA,OAAOsB,EAAI;AAAA,cACX,UAAUrB,KAAYqB,EAAI;AAAA,cAC1B,SAASC;AAAA,cACT,UAAU,MAAMb,EAAOY,EAAI,KAAK;AAAA,cAChC,WAAW,CAACI,MAAMb,EAAUa,GAAGT,CAAC;AAAA,cAChC,cAAYM,IAAY,YAAY;AAAA,YAAA;AAAA,UAAA;AAAA,UAGtC,gBAAAF,EAAC,QAAA,EAAK,WAAU,kBAAkB,YAAI,MAAA,CAAM;AAAA,QAAA;AAAA,MAAA;AAAA,MAnBvCC,EAAI;AAAA,IAAA;AAAA,EAsBf,CAAC,EAAA,CACH;AAEJ;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Rating.cjs","sources":["
|
|
1
|
+
{"version":3,"file":"Rating.cjs","sources":["../../../src/components/rating/Rating.tsx"],"sourcesContent":["/**\r\n * Rating.tsx — FINAL VERSION\r\n * ---------------------------\r\n * Features:\r\n * - Click to select\r\n * - Hover preview\r\n * - Keyboard navigation\r\n * - Theme-aware\r\n * - Accessible\r\n */\r\n\r\nimport React, { useState } from 'react';\r\nimport './Rating.css';\r\n\r\nexport interface RatingProps {\r\n /** Current value (1–max). If undefined → uncontrolled mode */\r\n value?: number;\r\n\r\n /** Max number of stars */\r\n max?: number;\r\n\r\n /** Change handler */\r\n onChange?: (value: number) => void;\r\n\r\n /** Custom icon */\r\n icon?: React.ReactNode;\r\n\r\n /** Custom filled icon */\r\n iconFilled?: React.ReactNode;\r\n\r\n /** Size */\r\n size?: 'sm' | 'md' | 'lg';\r\n\r\n /** Optional class */\r\n className?: string;\r\n}\r\n\r\nexport function Rating({\r\n value,\r\n max = 5,\r\n onChange,\r\n icon = '☆',\r\n iconFilled = '★',\r\n size = 'md',\r\n className = '',\r\n}: RatingProps) {\r\n // uncontrolled internal value\r\n const [internal, setInternal] = useState(0);\r\n const current = value ?? internal;\r\n\r\n const [hover, setHover] = useState<number | null>(null);\r\n\r\n const finalValue = hover ?? current;\r\n\r\n const handleSelect = (v: number) => {\r\n if (value === undefined) setInternal(v);\r\n onChange?.(v);\r\n };\r\n\r\n const handleKey = (e: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (e.key === 'ArrowRight') handleSelect(Math.min((current ?? 0) + 1, max));\r\n if (e.key === 'ArrowLeft') handleSelect(Math.max((current ?? 0) - 1, 1));\r\n };\r\n\r\n return (\r\n <div\r\n className={`ui-rating ui-rating--${size} ${className}`}\r\n role=\"radiogroup\"\r\n tabIndex={0}\r\n onKeyDown={handleKey}\r\n aria-label=\"Rating\"\r\n >\r\n {Array.from({ length: max }, (_, i) => {\r\n const index = i + 1;\r\n const filled = index <= finalValue;\r\n\r\n return (\r\n <span\r\n key={index}\r\n role=\"radio\"\r\n aria-checked={current === index}\r\n tabIndex={-1}\r\n className={`ui-rating-item ${filled ? 'filled' : ''}`}\r\n onMouseEnter={() => setHover(index)}\r\n onMouseLeave={() => setHover(null)}\r\n onClick={() => handleSelect(index)}\r\n >\r\n {filled ? iconFilled : icon}\r\n </span>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["Rating","value","max","onChange","icon","iconFilled","size","className","internal","setInternal","useState","current","hover","setHover","finalValue","handleSelect","v","handleKey","jsx","_","i","index","filled"],"mappings":"oKAqCO,SAASA,EAAO,CACrB,MAAAC,EACA,IAAAC,EAAM,EACN,SAAAC,EACA,KAAAC,EAAO,IACP,WAAAC,EAAa,IACb,KAAAC,EAAO,KACP,UAAAC,EAAY,EACd,EAAgB,CAEd,KAAM,CAACC,EAAUC,CAAW,EAAIC,EAAAA,SAAS,CAAC,EACpCC,EAAUV,GAASO,EAEnB,CAACI,EAAOC,CAAQ,EAAIH,EAAAA,SAAwB,IAAI,EAEhDI,EAAaF,GAASD,EAEtBI,EAAgBC,GAAc,CAC9Bf,IAAU,QAAWQ,EAAYO,CAAC,EACtCb,IAAWa,CAAC,CACd,EAEMC,EAAa,GAA2C,CACxD,EAAE,MAAQ,cAAcF,EAAa,KAAK,KAAKJ,GAAW,GAAK,EAAGT,CAAG,CAAC,EACtE,EAAE,MAAQ,aAAaa,EAAa,KAAK,KAAKJ,GAAW,GAAK,EAAG,CAAC,CAAC,CACzE,EAEA,OACEO,EAAAA,IAAC,MAAA,CACC,UAAW,wBAAwBZ,CAAI,IAAIC,CAAS,GACpD,KAAK,aACL,SAAU,EACV,UAAWU,EACX,aAAW,SAEV,SAAA,MAAM,KAAK,CAAE,OAAQf,GAAO,CAACiB,EAAGC,IAAM,CACrC,MAAMC,EAAQD,EAAI,EACZE,EAASD,GAASP,EAExB,OACEI,EAAAA,IAAC,OAAA,CAEC,KAAK,QACL,eAAcP,IAAYU,EAC1B,SAAU,GACV,UAAW,kBAAkBC,EAAS,SAAW,EAAE,GACnD,aAAc,IAAMT,EAASQ,CAAK,EAClC,aAAc,IAAMR,EAAS,IAAI,EACjC,QAAS,IAAME,EAAaM,CAAK,EAEhC,WAAShB,EAAaD,CAAA,EATlBiB,CAAA,CAYX,CAAC,CAAA,CAAA,CAGP"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Rating.js","sources":["
|
|
1
|
+
{"version":3,"file":"Rating.js","sources":["../../../src/components/rating/Rating.tsx"],"sourcesContent":["/**\r\n * Rating.tsx — FINAL VERSION\r\n * ---------------------------\r\n * Features:\r\n * - Click to select\r\n * - Hover preview\r\n * - Keyboard navigation\r\n * - Theme-aware\r\n * - Accessible\r\n */\r\n\r\nimport React, { useState } from 'react';\r\nimport './Rating.css';\r\n\r\nexport interface RatingProps {\r\n /** Current value (1–max). If undefined → uncontrolled mode */\r\n value?: number;\r\n\r\n /** Max number of stars */\r\n max?: number;\r\n\r\n /** Change handler */\r\n onChange?: (value: number) => void;\r\n\r\n /** Custom icon */\r\n icon?: React.ReactNode;\r\n\r\n /** Custom filled icon */\r\n iconFilled?: React.ReactNode;\r\n\r\n /** Size */\r\n size?: 'sm' | 'md' | 'lg';\r\n\r\n /** Optional class */\r\n className?: string;\r\n}\r\n\r\nexport function Rating({\r\n value,\r\n max = 5,\r\n onChange,\r\n icon = '☆',\r\n iconFilled = '★',\r\n size = 'md',\r\n className = '',\r\n}: RatingProps) {\r\n // uncontrolled internal value\r\n const [internal, setInternal] = useState(0);\r\n const current = value ?? internal;\r\n\r\n const [hover, setHover] = useState<number | null>(null);\r\n\r\n const finalValue = hover ?? current;\r\n\r\n const handleSelect = (v: number) => {\r\n if (value === undefined) setInternal(v);\r\n onChange?.(v);\r\n };\r\n\r\n const handleKey = (e: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (e.key === 'ArrowRight') handleSelect(Math.min((current ?? 0) + 1, max));\r\n if (e.key === 'ArrowLeft') handleSelect(Math.max((current ?? 0) - 1, 1));\r\n };\r\n\r\n return (\r\n <div\r\n className={`ui-rating ui-rating--${size} ${className}`}\r\n role=\"radiogroup\"\r\n tabIndex={0}\r\n onKeyDown={handleKey}\r\n aria-label=\"Rating\"\r\n >\r\n {Array.from({ length: max }, (_, i) => {\r\n const index = i + 1;\r\n const filled = index <= finalValue;\r\n\r\n return (\r\n <span\r\n key={index}\r\n role=\"radio\"\r\n aria-checked={current === index}\r\n tabIndex={-1}\r\n className={`ui-rating-item ${filled ? 'filled' : ''}`}\r\n onMouseEnter={() => setHover(index)}\r\n onMouseLeave={() => setHover(null)}\r\n onClick={() => handleSelect(index)}\r\n >\r\n {filled ? iconFilled : icon}\r\n </span>\r\n );\r\n })}\r\n </div>\r\n );\r\n}\r\n"],"names":["Rating","value","max","onChange","icon","iconFilled","size","className","internal","setInternal","useState","current","hover","setHover","finalValue","handleSelect","v","handleKey","jsx","_","i","index","filled"],"mappings":";;;AAqCO,SAASA,EAAO;AAAA,EACrB,OAAAC;AAAA,EACA,KAAAC,IAAM;AAAA,EACN,UAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,YAAAC,IAAa;AAAA,EACb,MAAAC,IAAO;AAAA,EACP,WAAAC,IAAY;AACd,GAAgB;AAEd,QAAM,CAACC,GAAUC,CAAW,IAAIC,EAAS,CAAC,GACpCC,IAAUV,KAASO,GAEnB,CAACI,GAAOC,CAAQ,IAAIH,EAAwB,IAAI,GAEhDI,IAAaF,KAASD,GAEtBI,IAAe,CAACC,MAAc;AAClC,IAAIf,MAAU,UAAWQ,EAAYO,CAAC,GACtCb,IAAWa,CAAC;AAAA,EACd,GAEMC,IAAY,CAAC,MAA2C;AAC5D,IAAI,EAAE,QAAQ,gBAAcF,EAAa,KAAK,KAAKJ,KAAW,KAAK,GAAGT,CAAG,CAAC,GACtE,EAAE,QAAQ,eAAaa,EAAa,KAAK,KAAKJ,KAAW,KAAK,GAAG,CAAC,CAAC;AAAA,EACzE;AAEA,SACE,gBAAAO;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,wBAAwBZ,CAAI,IAAIC,CAAS;AAAA,MACpD,MAAK;AAAA,MACL,UAAU;AAAA,MACV,WAAWU;AAAA,MACX,cAAW;AAAA,MAEV,UAAA,MAAM,KAAK,EAAE,QAAQf,KAAO,CAACiB,GAAGC,MAAM;AACrC,cAAMC,IAAQD,IAAI,GACZE,IAASD,KAASP;AAExB,eACE,gBAAAI;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,MAAK;AAAA,YACL,gBAAcP,MAAYU;AAAA,YAC1B,UAAU;AAAA,YACV,WAAW,kBAAkBC,IAAS,WAAW,EAAE;AAAA,YACnD,cAAc,MAAMT,EAASQ,CAAK;AAAA,YAClC,cAAc,MAAMR,EAAS,IAAI;AAAA,YACjC,SAAS,MAAME,EAAaM,CAAK;AAAA,YAEhC,cAAShB,IAAaD;AAAA,UAAA;AAAA,UATlBiB;AAAA,QAAA;AAAA,MAYX,CAAC;AAAA,IAAA;AAAA,EAAA;AAGP;"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("react/jsx-runtime"),l=require("react");;/* empty css */const z=require("../../utils/restorefocus/restoreFocus.cjs"),G=require("../../utils/onclickoutside/onClickOutside.cjs"),J=require("../../utils/portal/portal.cjs");function L(s,h,g){const o=s.length;let d=h;for(let r=0;r<o;r++)if(d=(d+g+o)%o,!s[d].disabled)return d;return-1}function Q({options:s,value:h,defaultValue:g,onChange:o,placeholder:d="Select...",disabled:r=!1,name:C,id:O,className:T=""}){const I=h!==void 0,[$,K]=l.useState(g),b=I?h:$,S=s.find(e=>e.value===b)??null,[i,x]=l.useState(!1),[a,f]=l.useState(()=>s.findIndex(e=>!e.disabled)),v=l.useRef({buffer:"",lastTime:0}),p=l.useRef(null),m=l.useRef(null),P=l.useId(),R=O??P,y=`${R}-listbox`,E=`${R}-label`,V=l.useCallback(()=>{r||x(!0)},[r]),F=l.useCallback(()=>{x(!1),z.restoreFocus(p.current)},[]),M=l.useCallback(()=>{r||x(e=>!e)},[r]);l.useEffect(()=>{if(i)return G.onClickOutside(m,()=>x(!1))},[i]),l.useEffect(()=>{if(!i)return;const e=s.findIndex(n=>n.value===b&&!n.disabled),t=e>=0?e:s.findIndex(n=>!n.disabled);f(t>=0?t:-1),setTimeout(()=>{m.current?.focus()},0)},[i,s,b]);const N=l.useCallback(e=>{if(e<0||e>=s.length)return;const t=s[e];t.disabled||(I||K(t.value),o?.(t.value),x(!1),p.current?.focus())},[I,s,o]),U=e=>{r||["ArrowDown","ArrowUp"," ","Enter"].includes(e.key)&&(e.preventDefault(),V())},B=e=>{const t=e.key;if(t==="ArrowDown"){e.preventDefault();const n=L(s,a,1);n>=0&&f(n)}else if(t==="ArrowUp"){e.preventDefault();const n=L(s,a,-1);n>=0&&f(n)}else if(t==="Home"){e.preventDefault();const n=s.findIndex(u=>!u.disabled);n>=0&&f(n)}else if(t==="End"){e.preventDefault();const n=s.length-1-[...s].reverse().findIndex(u=>!u.disabled);n>=0&&f(n)}else if(t==="Enter"||t===" ")e.preventDefault(),a>=0&&N(a);else if(t==="Escape")e.preventDefault(),F();else if(t.length===1&&t.match(/\S/)){const n=Date.now();n-v.current.lastTime>700&&(v.current.buffer=""),v.current.buffer+=t.toLowerCase(),v.current.lastTime=n;const u=v.current.buffer,w=a>=0?a+1:0,A=s.length;for(let k=0;k<A;k++){const j=(w+k)%A,W=String(s[j].label).toLowerCase();if(!s[j].disabled&&W.startsWith(u)){f(j);break}}}},H=e=>{N(e)},[D,q]=l.useState(null);return l.useEffect(()=>{if(!i||!p.current){q(null);return}const e=p.current.getBoundingClientRect(),t=e.bottom+6,n=e.left;q({top:t,left:n})},[i]),c.jsxs("div",{className:`ui-select ${T}`,children:[C&&c.jsx("input",{type:"hidden",name:C,value:b??""}),c.jsxs("button",{ref:p,id:E,type:"button",className:"ui-select-trigger","aria-haspopup":"listbox","aria-expanded":i,"aria-controls":i?y:void 0,"aria-disabled":r||void 0,onClick:()=>M(),onKeyDown:U,disabled:r,children:[c.jsx("span",{children:S?c.jsx("span",{children:S.label}):c.jsx("span",{className:"ui-select-placeholder",children:d})}),c.jsx("span",{className:"ui-select-chevron","aria-hidden":!0,children:"▾"})]}),i&&D&&c.jsx(J.Portal,{children:c.jsx("div",{id:y,ref:m,className:"ui-select-listbox",role:"listbox","aria-labelledby":E,tabIndex:0,style:{top:D.top,left:D.left},onKeyDown:B,children:s.map((e,t)=>{const n=b===e.value,u=a===t;return c.jsx("div",{id:`${y}-option-${t}`,role:"option","aria-selected":n,"aria-disabled":e.disabled||void 0,className:"ui-select-option",tabIndex:-1,onMouseDown:w=>w.preventDefault(),onClick:()=>!e.disabled&&H(t),style:{outline:u?"2px solid var(--color-primary)":void 0},children:e.label},e.value)})})})]})}exports.Select=Q;
|
|
2
|
+
//# sourceMappingURL=Select.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Select.cjs","sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["/**\r\n * Select.tsx\r\n * ----------\r\n * Accessible single-select (listbox) component.\r\n *\r\n * Features:\r\n * - WAI-ARIA listbox pattern\r\n * - Keyboard navigation (ArrowUp/ArrowDown, Home, End, Enter, Space, Esc)\r\n * - Typeahead support\r\n * - Controlled + uncontrolled modes\r\n * - Portal rendering for the listbox\r\n * - Click outside to close\r\n * - Proper ARIA attributes and relationships\r\n * - Hidden input for form compatibility (optional name prop)\r\n *\r\n * Usage:\r\n * <Select\r\n * options={[{ value: 'a', label: 'Apple' }, { value: 'b', label: 'Banana' }]}\r\n * value={value} // optional controlled value\r\n * onChange={(v) => setValue(v)} // required if controlled\r\n * placeholder=\"Select fruit\"\r\n * name=\"fruit\" // optional, adds hidden input\r\n * />\r\n */\r\n\r\nimport React, {\r\n useState,\r\n useRef,\r\n useEffect,\r\n useCallback,\r\n KeyboardEvent,\r\n} from 'react';\r\nimport './Select.css';\r\nimport { Portal, onClickOutside, restoreFocus } from '../../utils/index';\r\n\r\ntype Option = {\r\n value: string;\r\n label: React.ReactNode;\r\n disabled?: boolean;\r\n};\r\n\r\ninterface SelectProps {\r\n options: Option[];\r\n value?: string; // controlled value\r\n defaultValue?: string; // uncontrolled initial\r\n onChange?: (value: string) => void;\r\n placeholder?: string;\r\n disabled?: boolean;\r\n name?: string; // if provided, render hidden input for forms\r\n id?: string;\r\n className?: string;\r\n}\r\n\r\n/* Helper: find next enabled index given direction */\r\nfunction findNextEnabled(options: Option[], start: number, direction: 1 | -1) {\r\n const len = options.length;\r\n let i = start;\r\n for (let step = 0; step < len; step++) {\r\n i = (i + direction + len) % len;\r\n if (!options[i].disabled) return i;\r\n }\r\n return -1;\r\n}\r\n\r\nexport function Select({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n placeholder = 'Select...',\r\n disabled = false,\r\n name,\r\n id,\r\n className = '',\r\n}: SelectProps) {\r\n // Controlled vs uncontrolled\r\n const isControlled = value !== undefined;\r\n const [internalValue, setInternalValue] = useState<string | undefined>(\r\n defaultValue\r\n );\r\n const selectedValue = isControlled ? value : internalValue;\r\n\r\n const selectedOption = options.find((o) => o.value === selectedValue) ?? null;\r\n\r\n const [open, setOpen] = useState(false);\r\n const [activeIndex, setActiveIndex] = useState<number>(() =>\r\n options.findIndex((o) => !o.disabled)\r\n );\r\n\r\n // Typeahead buffer\r\n const typeaheadRef = useRef({ buffer: '', lastTime: 0 });\r\n\r\n // Refs\r\n const triggerRef = useRef<HTMLButtonElement | null>(null);\r\n const listRef = useRef<HTMLDivElement | null>(null);\r\n\r\n // Unique ids\r\n const reactId = React.useId();\r\n const baseId = id ?? reactId;\r\n const listboxId = `${baseId}-listbox`;\r\n const labelId = `${baseId}-label`;\r\n\r\n // Open/close helpers\r\n const openList = useCallback(() => {\r\n if (disabled) return;\r\n setOpen(true);\r\n }, [disabled]);\r\n\r\n const closeList = useCallback(() => {\r\n setOpen(false);\r\n // restore focus to trigger\r\n restoreFocus(triggerRef.current);\r\n }, []);\r\n\r\n // Toggle\r\n const toggleList = useCallback(() => {\r\n if (disabled) return;\r\n setOpen((s) => !s);\r\n }, [disabled]);\r\n\r\n // Click outside to close\r\n useEffect(() => {\r\n if (!open) return;\r\n return onClickOutside(listRef, () => setOpen(false));\r\n }, [open]);\r\n\r\n // Ensure that when opening, activeIndex is valid and focus moves to listbox\r\n useEffect(() => {\r\n if (!open) return;\r\n // set active to selected or first enabled\r\n const selIndex = options.findIndex(\r\n (o) => o.value === selectedValue && !o.disabled\r\n );\r\n const idx =\r\n selIndex >= 0 ? selIndex : options.findIndex((o) => !o.disabled);\r\n setActiveIndex(idx >= 0 ? idx : -1);\r\n\r\n // focus the list container so keyboard events are captured\r\n setTimeout(() => {\r\n listRef.current?.focus();\r\n }, 0);\r\n }, [open, options, selectedValue]);\r\n\r\n // Selection handler\r\n const selectIndex = useCallback(\r\n (i: number) => {\r\n if (i < 0 || i >= options.length) return;\r\n const opt = options[i];\r\n if (opt.disabled) return;\r\n if (!isControlled) setInternalValue(opt.value);\r\n onChange?.(opt.value);\r\n setOpen(false);\r\n // restore focus\r\n triggerRef.current?.focus();\r\n },\r\n [isControlled, options, onChange]\r\n );\r\n\r\n // Keyboard handling when trigger has focus\r\n const onTriggerKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\r\n if (disabled) return;\r\n if (['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(e.key)) {\r\n e.preventDefault();\r\n openList();\r\n }\r\n };\r\n\r\n // Keyboard handling when listbox has focus\r\n const onListKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\r\n const key = e.key;\r\n if (key === 'ArrowDown') {\r\n e.preventDefault();\r\n const next = findNextEnabled(options, activeIndex, 1);\r\n if (next >= 0) setActiveIndex(next);\r\n } else if (key === 'ArrowUp') {\r\n e.preventDefault();\r\n const prev = findNextEnabled(options, activeIndex, -1);\r\n if (prev >= 0) setActiveIndex(prev);\r\n } else if (key === 'Home') {\r\n e.preventDefault();\r\n const first = options.findIndex((o) => !o.disabled);\r\n if (first >= 0) setActiveIndex(first);\r\n } else if (key === 'End') {\r\n e.preventDefault();\r\n const last =\r\n options.length -\r\n 1 -\r\n [...options].reverse().findIndex((o) => !o.disabled);\r\n if (last >= 0) setActiveIndex(last);\r\n } else if (key === 'Enter' || key === ' ') {\r\n e.preventDefault();\r\n if (activeIndex >= 0) selectIndex(activeIndex);\r\n } else if (key === 'Escape') {\r\n e.preventDefault();\r\n closeList();\r\n } else if (key.length === 1 && key.match(/\\S/)) {\r\n // typeahead: accumulate buffer of characters\r\n const now = Date.now();\r\n if (now - typeaheadRef.current.lastTime > 700) {\r\n typeaheadRef.current.buffer = '';\r\n }\r\n typeaheadRef.current.buffer += key.toLowerCase();\r\n typeaheadRef.current.lastTime = now;\r\n\r\n const buf = typeaheadRef.current.buffer;\r\n const start = activeIndex >= 0 ? activeIndex + 1 : 0;\r\n const len = options.length;\r\n // search forward from next item, wrapping\r\n for (let i = 0; i < len; i++) {\r\n const idx = (start + i) % len;\r\n const lab = String(options[idx].label).toLowerCase();\r\n if (!options[idx].disabled && lab.startsWith(buf)) {\r\n setActiveIndex(idx);\r\n break;\r\n }\r\n }\r\n }\r\n };\r\n\r\n // Click on option\r\n const onOptionClick = (i: number) => {\r\n selectIndex(i);\r\n };\r\n\r\n // compute listbox position based on trigger\r\n const [pos, setPos] = useState<{ top: number; left: number } | null>(null);\r\n useEffect(() => {\r\n if (!open || !triggerRef.current) {\r\n setPos(null);\r\n return;\r\n }\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const top = rect.bottom + 6;\r\n const left = rect.left;\r\n setPos({ top, left });\r\n }, [open]);\r\n\r\n // render\r\n return (\r\n <div className={`ui-select ${className}`}>\r\n {/* Hidden input for form compatibility */}\r\n {name && <input type=\"hidden\" name={name} value={selectedValue ?? ''} />}\r\n\r\n <button\r\n ref={triggerRef}\r\n id={labelId}\r\n type=\"button\"\r\n className=\"ui-select-trigger\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={open}\r\n aria-controls={open ? listboxId : undefined}\r\n aria-disabled={disabled || undefined}\r\n onClick={() => toggleList()}\r\n onKeyDown={onTriggerKeyDown}\r\n disabled={disabled}\r\n >\r\n <span>\r\n {selectedOption ? (\r\n <span>{selectedOption.label}</span>\r\n ) : (\r\n <span className=\"ui-select-placeholder\">{placeholder}</span>\r\n )}\r\n </span>\r\n <span className=\"ui-select-chevron\" aria-hidden>\r\n ▾\r\n </span>\r\n </button>\r\n\r\n {open && pos && (\r\n <Portal>\r\n <div\r\n id={listboxId}\r\n ref={listRef}\r\n className=\"ui-select-listbox\"\r\n role=\"listbox\"\r\n aria-labelledby={labelId}\r\n tabIndex={0}\r\n style={{\r\n top: pos.top,\r\n left: pos.left,\r\n }}\r\n onKeyDown={onListKeyDown}\r\n >\r\n {options.map((opt, i) => {\r\n const isSelected = selectedValue === opt.value;\r\n const isActive = activeIndex === i;\r\n return (\r\n <div\r\n key={opt.value}\r\n id={`${listboxId}-option-${i}`}\r\n role=\"option\"\r\n aria-selected={isSelected}\r\n aria-disabled={opt.disabled || undefined}\r\n className=\"ui-select-option\"\r\n tabIndex={-1}\r\n onMouseDown={(e) => e.preventDefault()} /* prevent blur */\r\n onClick={() => !opt.disabled && onOptionClick(i)}\r\n // Visual focus when active (not moving DOM focus)\r\n style={{\r\n outline: isActive\r\n ? '2px solid var(--color-primary)'\r\n : undefined,\r\n }}\r\n >\r\n {opt.label}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </Portal>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"names":["findNextEnabled","options","start","direction","len","i","step","Select","value","defaultValue","onChange","placeholder","disabled","name","id","className","isControlled","internalValue","setInternalValue","useState","selectedValue","selectedOption","o","open","setOpen","activeIndex","setActiveIndex","typeaheadRef","useRef","triggerRef","listRef","reactId","React","baseId","listboxId","labelId","openList","useCallback","closeList","restoreFocus","toggleList","s","useEffect","onClickOutside","selIndex","idx","selectIndex","opt","onTriggerKeyDown","onListKeyDown","key","next","prev","first","last","now","buf","lab","onOptionClick","pos","setPos","rect","top","left","jsxs","jsx","Portal","isSelected","isActive","e"],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { jsxs as V, jsx as i } from "react/jsx-runtime";
|
|
2
|
+
import X, { useState as x, useRef as A, useCallback as h, useEffect as E } from "react";
|
|
3
|
+
/* empty css */
|
|
4
|
+
import { restoreFocus as Y } from "../../utils/restorefocus/restoreFocus.js";
|
|
5
|
+
import { onClickOutside as Z } from "../../utils/onclickoutside/onClickOutside.js";
|
|
6
|
+
import { Portal as _ } from "../../utils/portal/portal.js";
|
|
7
|
+
function j(l, m, I) {
|
|
8
|
+
const o = l.length;
|
|
9
|
+
let f = m;
|
|
10
|
+
for (let r = 0; r < o; r++)
|
|
11
|
+
if (f = (f + I + o) % o, !l[f].disabled) return f;
|
|
12
|
+
return -1;
|
|
13
|
+
}
|
|
14
|
+
function ie({
|
|
15
|
+
options: l,
|
|
16
|
+
value: m,
|
|
17
|
+
defaultValue: I,
|
|
18
|
+
onChange: o,
|
|
19
|
+
placeholder: f = "Select...",
|
|
20
|
+
disabled: r = !1,
|
|
21
|
+
name: L,
|
|
22
|
+
id: P,
|
|
23
|
+
className: U = ""
|
|
24
|
+
}) {
|
|
25
|
+
const D = m !== void 0, [B, F] = x(
|
|
26
|
+
I
|
|
27
|
+
), d = D ? m : B, R = l.find((e) => e.value === d) ?? null, [s, b] = x(!1), [c, u] = x(
|
|
28
|
+
() => l.findIndex((e) => !e.disabled)
|
|
29
|
+
), v = A({ buffer: "", lastTime: 0 }), p = A(null), g = A(null), H = X.useId(), S = P ?? H, w = `${S}-listbox`, T = `${S}-label`, M = h(() => {
|
|
30
|
+
r || b(!0);
|
|
31
|
+
}, [r]), W = h(() => {
|
|
32
|
+
b(!1), Y(p.current);
|
|
33
|
+
}, []), q = h(() => {
|
|
34
|
+
r || b((e) => !e);
|
|
35
|
+
}, [r]);
|
|
36
|
+
E(() => {
|
|
37
|
+
if (s)
|
|
38
|
+
return Z(g, () => b(!1));
|
|
39
|
+
}, [s]), E(() => {
|
|
40
|
+
if (!s) return;
|
|
41
|
+
const e = l.findIndex(
|
|
42
|
+
(n) => n.value === d && !n.disabled
|
|
43
|
+
), t = e >= 0 ? e : l.findIndex((n) => !n.disabled);
|
|
44
|
+
u(t >= 0 ? t : -1), setTimeout(() => {
|
|
45
|
+
g.current?.focus();
|
|
46
|
+
}, 0);
|
|
47
|
+
}, [s, l, d]);
|
|
48
|
+
const $ = h(
|
|
49
|
+
(e) => {
|
|
50
|
+
if (e < 0 || e >= l.length) return;
|
|
51
|
+
const t = l[e];
|
|
52
|
+
t.disabled || (D || F(t.value), o?.(t.value), b(!1), p.current?.focus());
|
|
53
|
+
},
|
|
54
|
+
[D, l, o]
|
|
55
|
+
), z = (e) => {
|
|
56
|
+
r || ["ArrowDown", "ArrowUp", " ", "Enter"].includes(e.key) && (e.preventDefault(), M());
|
|
57
|
+
}, G = (e) => {
|
|
58
|
+
const t = e.key;
|
|
59
|
+
if (t === "ArrowDown") {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
const n = j(l, c, 1);
|
|
62
|
+
n >= 0 && u(n);
|
|
63
|
+
} else if (t === "ArrowUp") {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
const n = j(l, c, -1);
|
|
66
|
+
n >= 0 && u(n);
|
|
67
|
+
} else if (t === "Home") {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
const n = l.findIndex((a) => !a.disabled);
|
|
70
|
+
n >= 0 && u(n);
|
|
71
|
+
} else if (t === "End") {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
const n = l.length - 1 - [...l].reverse().findIndex((a) => !a.disabled);
|
|
74
|
+
n >= 0 && u(n);
|
|
75
|
+
} else if (t === "Enter" || t === " ")
|
|
76
|
+
e.preventDefault(), c >= 0 && $(c);
|
|
77
|
+
else if (t === "Escape")
|
|
78
|
+
e.preventDefault(), W();
|
|
79
|
+
else if (t.length === 1 && t.match(/\S/)) {
|
|
80
|
+
const n = Date.now();
|
|
81
|
+
n - v.current.lastTime > 700 && (v.current.buffer = ""), v.current.buffer += t.toLowerCase(), v.current.lastTime = n;
|
|
82
|
+
const a = v.current.buffer, k = c >= 0 ? c + 1 : 0, O = l.length;
|
|
83
|
+
for (let C = 0; C < O; C++) {
|
|
84
|
+
const N = (k + C) % O, Q = String(l[N].label).toLowerCase();
|
|
85
|
+
if (!l[N].disabled && Q.startsWith(a)) {
|
|
86
|
+
u(N);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}, J = (e) => {
|
|
92
|
+
$(e);
|
|
93
|
+
}, [y, K] = x(null);
|
|
94
|
+
return E(() => {
|
|
95
|
+
if (!s || !p.current) {
|
|
96
|
+
K(null);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const e = p.current.getBoundingClientRect(), t = e.bottom + 6, n = e.left;
|
|
100
|
+
K({ top: t, left: n });
|
|
101
|
+
}, [s]), /* @__PURE__ */ V("div", { className: `ui-select ${U}`, children: [
|
|
102
|
+
L && /* @__PURE__ */ i("input", { type: "hidden", name: L, value: d ?? "" }),
|
|
103
|
+
/* @__PURE__ */ V(
|
|
104
|
+
"button",
|
|
105
|
+
{
|
|
106
|
+
ref: p,
|
|
107
|
+
id: T,
|
|
108
|
+
type: "button",
|
|
109
|
+
className: "ui-select-trigger",
|
|
110
|
+
"aria-haspopup": "listbox",
|
|
111
|
+
"aria-expanded": s,
|
|
112
|
+
"aria-controls": s ? w : void 0,
|
|
113
|
+
"aria-disabled": r || void 0,
|
|
114
|
+
onClick: () => q(),
|
|
115
|
+
onKeyDown: z,
|
|
116
|
+
disabled: r,
|
|
117
|
+
children: [
|
|
118
|
+
/* @__PURE__ */ i("span", { children: R ? /* @__PURE__ */ i("span", { children: R.label }) : /* @__PURE__ */ i("span", { className: "ui-select-placeholder", children: f }) }),
|
|
119
|
+
/* @__PURE__ */ i("span", { className: "ui-select-chevron", "aria-hidden": !0, children: "▾" })
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
),
|
|
123
|
+
s && y && /* @__PURE__ */ i(_, { children: /* @__PURE__ */ i(
|
|
124
|
+
"div",
|
|
125
|
+
{
|
|
126
|
+
id: w,
|
|
127
|
+
ref: g,
|
|
128
|
+
className: "ui-select-listbox",
|
|
129
|
+
role: "listbox",
|
|
130
|
+
"aria-labelledby": T,
|
|
131
|
+
tabIndex: 0,
|
|
132
|
+
style: {
|
|
133
|
+
top: y.top,
|
|
134
|
+
left: y.left
|
|
135
|
+
},
|
|
136
|
+
onKeyDown: G,
|
|
137
|
+
children: l.map((e, t) => {
|
|
138
|
+
const n = d === e.value, a = c === t;
|
|
139
|
+
return /* @__PURE__ */ i(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
id: `${w}-option-${t}`,
|
|
143
|
+
role: "option",
|
|
144
|
+
"aria-selected": n,
|
|
145
|
+
"aria-disabled": e.disabled || void 0,
|
|
146
|
+
className: "ui-select-option",
|
|
147
|
+
tabIndex: -1,
|
|
148
|
+
onMouseDown: (k) => k.preventDefault(),
|
|
149
|
+
onClick: () => !e.disabled && J(t),
|
|
150
|
+
style: {
|
|
151
|
+
outline: a ? "2px solid var(--color-primary)" : void 0
|
|
152
|
+
},
|
|
153
|
+
children: e.label
|
|
154
|
+
},
|
|
155
|
+
e.value
|
|
156
|
+
);
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
) })
|
|
160
|
+
] });
|
|
161
|
+
}
|
|
162
|
+
export {
|
|
163
|
+
ie as Select
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=Select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Select.js","sources":["../../../src/components/select/Select.tsx"],"sourcesContent":["/**\r\n * Select.tsx\r\n * ----------\r\n * Accessible single-select (listbox) component.\r\n *\r\n * Features:\r\n * - WAI-ARIA listbox pattern\r\n * - Keyboard navigation (ArrowUp/ArrowDown, Home, End, Enter, Space, Esc)\r\n * - Typeahead support\r\n * - Controlled + uncontrolled modes\r\n * - Portal rendering for the listbox\r\n * - Click outside to close\r\n * - Proper ARIA attributes and relationships\r\n * - Hidden input for form compatibility (optional name prop)\r\n *\r\n * Usage:\r\n * <Select\r\n * options={[{ value: 'a', label: 'Apple' }, { value: 'b', label: 'Banana' }]}\r\n * value={value} // optional controlled value\r\n * onChange={(v) => setValue(v)} // required if controlled\r\n * placeholder=\"Select fruit\"\r\n * name=\"fruit\" // optional, adds hidden input\r\n * />\r\n */\r\n\r\nimport React, {\r\n useState,\r\n useRef,\r\n useEffect,\r\n useCallback,\r\n KeyboardEvent,\r\n} from 'react';\r\nimport './Select.css';\r\nimport { Portal, onClickOutside, restoreFocus } from '../../utils/index';\r\n\r\ntype Option = {\r\n value: string;\r\n label: React.ReactNode;\r\n disabled?: boolean;\r\n};\r\n\r\ninterface SelectProps {\r\n options: Option[];\r\n value?: string; // controlled value\r\n defaultValue?: string; // uncontrolled initial\r\n onChange?: (value: string) => void;\r\n placeholder?: string;\r\n disabled?: boolean;\r\n name?: string; // if provided, render hidden input for forms\r\n id?: string;\r\n className?: string;\r\n}\r\n\r\n/* Helper: find next enabled index given direction */\r\nfunction findNextEnabled(options: Option[], start: number, direction: 1 | -1) {\r\n const len = options.length;\r\n let i = start;\r\n for (let step = 0; step < len; step++) {\r\n i = (i + direction + len) % len;\r\n if (!options[i].disabled) return i;\r\n }\r\n return -1;\r\n}\r\n\r\nexport function Select({\r\n options,\r\n value,\r\n defaultValue,\r\n onChange,\r\n placeholder = 'Select...',\r\n disabled = false,\r\n name,\r\n id,\r\n className = '',\r\n}: SelectProps) {\r\n // Controlled vs uncontrolled\r\n const isControlled = value !== undefined;\r\n const [internalValue, setInternalValue] = useState<string | undefined>(\r\n defaultValue\r\n );\r\n const selectedValue = isControlled ? value : internalValue;\r\n\r\n const selectedOption = options.find((o) => o.value === selectedValue) ?? null;\r\n\r\n const [open, setOpen] = useState(false);\r\n const [activeIndex, setActiveIndex] = useState<number>(() =>\r\n options.findIndex((o) => !o.disabled)\r\n );\r\n\r\n // Typeahead buffer\r\n const typeaheadRef = useRef({ buffer: '', lastTime: 0 });\r\n\r\n // Refs\r\n const triggerRef = useRef<HTMLButtonElement | null>(null);\r\n const listRef = useRef<HTMLDivElement | null>(null);\r\n\r\n // Unique ids\r\n const reactId = React.useId();\r\n const baseId = id ?? reactId;\r\n const listboxId = `${baseId}-listbox`;\r\n const labelId = `${baseId}-label`;\r\n\r\n // Open/close helpers\r\n const openList = useCallback(() => {\r\n if (disabled) return;\r\n setOpen(true);\r\n }, [disabled]);\r\n\r\n const closeList = useCallback(() => {\r\n setOpen(false);\r\n // restore focus to trigger\r\n restoreFocus(triggerRef.current);\r\n }, []);\r\n\r\n // Toggle\r\n const toggleList = useCallback(() => {\r\n if (disabled) return;\r\n setOpen((s) => !s);\r\n }, [disabled]);\r\n\r\n // Click outside to close\r\n useEffect(() => {\r\n if (!open) return;\r\n return onClickOutside(listRef, () => setOpen(false));\r\n }, [open]);\r\n\r\n // Ensure that when opening, activeIndex is valid and focus moves to listbox\r\n useEffect(() => {\r\n if (!open) return;\r\n // set active to selected or first enabled\r\n const selIndex = options.findIndex(\r\n (o) => o.value === selectedValue && !o.disabled\r\n );\r\n const idx =\r\n selIndex >= 0 ? selIndex : options.findIndex((o) => !o.disabled);\r\n setActiveIndex(idx >= 0 ? idx : -1);\r\n\r\n // focus the list container so keyboard events are captured\r\n setTimeout(() => {\r\n listRef.current?.focus();\r\n }, 0);\r\n }, [open, options, selectedValue]);\r\n\r\n // Selection handler\r\n const selectIndex = useCallback(\r\n (i: number) => {\r\n if (i < 0 || i >= options.length) return;\r\n const opt = options[i];\r\n if (opt.disabled) return;\r\n if (!isControlled) setInternalValue(opt.value);\r\n onChange?.(opt.value);\r\n setOpen(false);\r\n // restore focus\r\n triggerRef.current?.focus();\r\n },\r\n [isControlled, options, onChange]\r\n );\r\n\r\n // Keyboard handling when trigger has focus\r\n const onTriggerKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\r\n if (disabled) return;\r\n if (['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(e.key)) {\r\n e.preventDefault();\r\n openList();\r\n }\r\n };\r\n\r\n // Keyboard handling when listbox has focus\r\n const onListKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\r\n const key = e.key;\r\n if (key === 'ArrowDown') {\r\n e.preventDefault();\r\n const next = findNextEnabled(options, activeIndex, 1);\r\n if (next >= 0) setActiveIndex(next);\r\n } else if (key === 'ArrowUp') {\r\n e.preventDefault();\r\n const prev = findNextEnabled(options, activeIndex, -1);\r\n if (prev >= 0) setActiveIndex(prev);\r\n } else if (key === 'Home') {\r\n e.preventDefault();\r\n const first = options.findIndex((o) => !o.disabled);\r\n if (first >= 0) setActiveIndex(first);\r\n } else if (key === 'End') {\r\n e.preventDefault();\r\n const last =\r\n options.length -\r\n 1 -\r\n [...options].reverse().findIndex((o) => !o.disabled);\r\n if (last >= 0) setActiveIndex(last);\r\n } else if (key === 'Enter' || key === ' ') {\r\n e.preventDefault();\r\n if (activeIndex >= 0) selectIndex(activeIndex);\r\n } else if (key === 'Escape') {\r\n e.preventDefault();\r\n closeList();\r\n } else if (key.length === 1 && key.match(/\\S/)) {\r\n // typeahead: accumulate buffer of characters\r\n const now = Date.now();\r\n if (now - typeaheadRef.current.lastTime > 700) {\r\n typeaheadRef.current.buffer = '';\r\n }\r\n typeaheadRef.current.buffer += key.toLowerCase();\r\n typeaheadRef.current.lastTime = now;\r\n\r\n const buf = typeaheadRef.current.buffer;\r\n const start = activeIndex >= 0 ? activeIndex + 1 : 0;\r\n const len = options.length;\r\n // search forward from next item, wrapping\r\n for (let i = 0; i < len; i++) {\r\n const idx = (start + i) % len;\r\n const lab = String(options[idx].label).toLowerCase();\r\n if (!options[idx].disabled && lab.startsWith(buf)) {\r\n setActiveIndex(idx);\r\n break;\r\n }\r\n }\r\n }\r\n };\r\n\r\n // Click on option\r\n const onOptionClick = (i: number) => {\r\n selectIndex(i);\r\n };\r\n\r\n // compute listbox position based on trigger\r\n const [pos, setPos] = useState<{ top: number; left: number } | null>(null);\r\n useEffect(() => {\r\n if (!open || !triggerRef.current) {\r\n setPos(null);\r\n return;\r\n }\r\n const rect = triggerRef.current.getBoundingClientRect();\r\n const top = rect.bottom + 6;\r\n const left = rect.left;\r\n setPos({ top, left });\r\n }, [open]);\r\n\r\n // render\r\n return (\r\n <div className={`ui-select ${className}`}>\r\n {/* Hidden input for form compatibility */}\r\n {name && <input type=\"hidden\" name={name} value={selectedValue ?? ''} />}\r\n\r\n <button\r\n ref={triggerRef}\r\n id={labelId}\r\n type=\"button\"\r\n className=\"ui-select-trigger\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={open}\r\n aria-controls={open ? listboxId : undefined}\r\n aria-disabled={disabled || undefined}\r\n onClick={() => toggleList()}\r\n onKeyDown={onTriggerKeyDown}\r\n disabled={disabled}\r\n >\r\n <span>\r\n {selectedOption ? (\r\n <span>{selectedOption.label}</span>\r\n ) : (\r\n <span className=\"ui-select-placeholder\">{placeholder}</span>\r\n )}\r\n </span>\r\n <span className=\"ui-select-chevron\" aria-hidden>\r\n ▾\r\n </span>\r\n </button>\r\n\r\n {open && pos && (\r\n <Portal>\r\n <div\r\n id={listboxId}\r\n ref={listRef}\r\n className=\"ui-select-listbox\"\r\n role=\"listbox\"\r\n aria-labelledby={labelId}\r\n tabIndex={0}\r\n style={{\r\n top: pos.top,\r\n left: pos.left,\r\n }}\r\n onKeyDown={onListKeyDown}\r\n >\r\n {options.map((opt, i) => {\r\n const isSelected = selectedValue === opt.value;\r\n const isActive = activeIndex === i;\r\n return (\r\n <div\r\n key={opt.value}\r\n id={`${listboxId}-option-${i}`}\r\n role=\"option\"\r\n aria-selected={isSelected}\r\n aria-disabled={opt.disabled || undefined}\r\n className=\"ui-select-option\"\r\n tabIndex={-1}\r\n onMouseDown={(e) => e.preventDefault()} /* prevent blur */\r\n onClick={() => !opt.disabled && onOptionClick(i)}\r\n // Visual focus when active (not moving DOM focus)\r\n style={{\r\n outline: isActive\r\n ? '2px solid var(--color-primary)'\r\n : undefined,\r\n }}\r\n >\r\n {opt.label}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </Portal>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"names":["findNextEnabled","options","start","direction","len","i","step","Select","value","defaultValue","onChange","placeholder","disabled","name","id","className","isControlled","internalValue","setInternalValue","useState","selectedValue","selectedOption","o","open","setOpen","activeIndex","setActiveIndex","typeaheadRef","useRef","triggerRef","listRef","reactId","React","baseId","listboxId","labelId","openList","useCallback","closeList","restoreFocus","toggleList","s","useEffect","onClickOutside","selIndex","idx","selectIndex","opt","onTriggerKeyDown","onListKeyDown","key","next","prev","first","last","now","buf","lab","onOptionClick","pos","setPos","rect","top","left","jsxs","jsx","Portal","isSelected","isActive","e"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Skeleton.cjs","sources":["
|
|
1
|
+
{"version":3,"file":"Skeleton.cjs","sources":["../../../src/components/skeleton/Skeleton.tsx"],"sourcesContent":["/**\r\n * Skeleton.tsx\r\n * Lightweight, accessible skeleton (placeholder) component.\r\n *\r\n * Props:\r\n * - size: visual thickness (xs, sm, md, lg, xl)\r\n * - width: number | string (e.g. 200, \"50%\", \"100px\")\r\n * - animated: whether to show shimmer (respects prefers-reduced-motion)\r\n * - circle: renders as a circle (useful for avatars)\r\n * - className: pass-through for custom styling\r\n *\r\n * Accessibility:\r\n * - role=\"status\" with aria-busy on parent usage is recommended by developer.\r\n * - For individual skeletons that should be ignored by assistive tech set aria-hidden=true.\r\n */\r\n/**\r\n * Skeleton.tsx + Helper Components\r\n * --------------------------------\r\n * Adds:\r\n * - Skeleton.Text\r\n * - Skeleton.Paragraph\r\n * - Skeleton.Avatar\r\n * - Skeleton.Button\r\n * - Skeleton.Card\r\n */\r\n\r\nimport React from 'react';\r\nimport './Skeleton.css';\r\n\r\ntype Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n\r\ninterface SkeletonProps {\r\n size?: Size;\r\n width?: number | string;\r\n animated?: boolean;\r\n circle?: boolean;\r\n ariaHidden?: boolean;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n}\r\n\r\nfunction formatWidth(w?: number | string): string | undefined {\r\n if (w === undefined) return undefined;\r\n if (typeof w === 'number') return `${w}px`;\r\n return w;\r\n}\r\n\r\nexport function Skeleton({\r\n size = 'md',\r\n width,\r\n animated = true,\r\n circle = false,\r\n ariaHidden = true,\r\n className = '',\r\n style,\r\n}: SkeletonProps) {\r\n const classes = [\r\n 'ui-skeleton',\r\n `ui-skeleton--${size}`,\r\n animated && 'ui-skeleton--animated',\r\n circle && 'ui-skeleton--circle',\r\n className,\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <span\r\n className={classes}\r\n style={{\r\n width: formatWidth(width),\r\n display: width ? 'block' : undefined,\r\n ...style,\r\n }}\r\n aria-hidden={ariaHidden}\r\n role=\"presentation\"\r\n />\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Helper Components\r\n * ------------------------------------------------------*/\r\n\r\n/**\r\n * Simple text line.\r\n * Default width: 100%\r\n */\r\nfunction Text({\r\n width = '100%',\r\n size = 'md',\r\n}: {\r\n width?: number | string;\r\n size?: Size;\r\n}) {\r\n return <Skeleton width={width} size={size} />;\r\n}\r\n\r\n/**\r\n * Paragraph: multiple lines of text skeleton\r\n * lines default = 3\r\n */\r\nfunction Paragraph({\r\n lines = 3,\r\n size = 'md',\r\n}: {\r\n lines?: number;\r\n size?: Size;\r\n}) {\r\n const arr = Array.from({ length: lines });\r\n\r\n return (\r\n <div aria-hidden=\"true\">\r\n {arr.map((_, i) => (\r\n <div key={i} style={{ marginBlockEnd: '8px' }}>\r\n <Skeleton width={i === lines - 1 ? '75%' : '100%'} size={size} />\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Avatar placeholder, circle with dynamic size\r\n */\r\nfunction AvatarSkeleton({ size = 40 }: { size?: number }) {\r\n return <Skeleton circle width={size} animated />;\r\n}\r\n\r\n/**\r\n * Button skeleton (pill-like shape)\r\n */\r\nfunction ButtonSkeleton({\r\n width = 100,\r\n height = 36,\r\n}: {\r\n width?: number;\r\n height?: number;\r\n}) {\r\n return (\r\n <Skeleton\r\n width={width}\r\n size=\"md\"\r\n animated\r\n style={{\r\n height,\r\n borderRadius: 'var(--radius-md)',\r\n }}\r\n />\r\n );\r\n}\r\n\r\n/**\r\n * Card Skeleton: header + lines + footer bar\r\n */\r\nfunction CardSkeleton() {\r\n return (\r\n <div aria-hidden=\"true\">\r\n <Skeleton width=\"60%\" size=\"lg\" style={{ marginBottom: 12 }} />\r\n <Paragraph lines={3} />\r\n <Skeleton width=\"40%\" size=\"md\" style={{ marginTop: 16 }} />\r\n </div>\r\n );\r\n}\r\n\r\n/* Attach helpers */\r\nSkeleton.Text = Text;\r\nSkeleton.Paragraph = Paragraph;\r\nSkeleton.Avatar = AvatarSkeleton;\r\nSkeleton.Button = ButtonSkeleton;\r\nSkeleton.Card = CardSkeleton;\r\n\r\nexport default Skeleton;\r\n"],"names":["formatWidth","w","Skeleton","size","width","animated","circle","ariaHidden","className","style","classes","jsx","Text","Paragraph","lines","arr","_","AvatarSkeleton","ButtonSkeleton","height","CardSkeleton","jsxs"],"mappings":"+KAyCA,SAASA,EAAYC,EAAyC,CAC5D,GAAIA,IAAM,OACV,OAAI,OAAOA,GAAM,SAAiB,GAAGA,CAAC,KAC/BA,CACT,CAEO,SAASC,EAAS,CACvB,KAAAC,EAAO,KACP,MAAAC,EACA,SAAAC,EAAW,GACX,OAAAC,EAAS,GACT,WAAAC,EAAa,GACb,UAAAC,EAAY,GACZ,MAAAC,CACF,EAAkB,CAChB,MAAMC,EAAU,CACd,cACA,gBAAgBP,CAAI,GACpBE,GAAY,wBACZC,GAAU,sBACVE,CAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG,EAEX,OACEG,EAAAA,IAAC,OAAA,CACC,UAAWD,EACX,MAAO,CACL,MAAOV,EAAYI,CAAK,EACxB,QAASA,EAAQ,QAAU,OAC3B,GAAGK,CAAA,EAEL,cAAaF,EACb,KAAK,cAAA,CAAA,CAGX,CAUA,SAASK,EAAK,CACZ,MAAAR,EAAQ,OACR,KAAAD,EAAO,IACT,EAGG,CACD,OAAOQ,EAAAA,IAACT,EAAA,CAAS,MAAAE,EAAc,KAAAD,CAAA,CAAY,CAC7C,CAMA,SAASU,EAAU,CACjB,MAAAC,EAAQ,EACR,KAAAX,EAAO,IACT,EAGG,CACD,MAAMY,EAAM,MAAM,KAAK,CAAE,OAAQD,EAAO,EAExC,OACEH,EAAAA,IAAC,MAAA,CAAI,cAAY,OACd,SAAAI,EAAI,IAAI,CAACC,EAAG,IACXL,EAAAA,IAAC,MAAA,CAAY,MAAO,CAAE,eAAgB,KAAA,EACpC,SAAAA,EAAAA,IAACT,EAAA,CAAS,MAAO,IAAMY,EAAQ,EAAI,MAAQ,OAAQ,KAAAX,CAAA,CAAY,CAAA,EADvD,CAEV,CACD,EACH,CAEJ,CAKA,SAASc,EAAe,CAAE,KAAAd,EAAO,IAAyB,CACxD,aAAQD,EAAA,CAAS,OAAM,GAAC,MAAOC,EAAM,SAAQ,GAAC,CAChD,CAKA,SAASe,EAAe,CACtB,MAAAd,EAAQ,IACR,OAAAe,EAAS,EACX,EAGG,CACD,OACER,EAAAA,IAACT,EAAA,CACC,MAAAE,EACA,KAAK,KACL,SAAQ,GACR,MAAO,CACL,OAAAe,EACA,aAAc,kBAAA,CAChB,CAAA,CAGN,CAKA,SAASC,GAAe,CACtB,OACEC,EAAAA,KAAC,MAAA,CAAI,cAAY,OACf,SAAA,CAAAV,EAAAA,IAACT,EAAA,CAAS,MAAM,MAAM,KAAK,KAAK,MAAO,CAAE,aAAc,EAAA,CAAG,CAAG,EAC7DS,EAAAA,IAACE,EAAA,CAAU,MAAO,CAAA,CAAG,EACrBF,EAAAA,IAACT,EAAA,CAAS,MAAM,MAAM,KAAK,KAAK,MAAO,CAAE,UAAW,GAAG,CAAG,CAAA,EAC5D,CAEJ,CAGAA,EAAS,KAAOU,EAChBV,EAAS,UAAYW,EACrBX,EAAS,OAASe,EAClBf,EAAS,OAASgB,EAClBhB,EAAS,KAAOkB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Skeleton.js","sources":["
|
|
1
|
+
{"version":3,"file":"Skeleton.js","sources":["../../../src/components/skeleton/Skeleton.tsx"],"sourcesContent":["/**\r\n * Skeleton.tsx\r\n * Lightweight, accessible skeleton (placeholder) component.\r\n *\r\n * Props:\r\n * - size: visual thickness (xs, sm, md, lg, xl)\r\n * - width: number | string (e.g. 200, \"50%\", \"100px\")\r\n * - animated: whether to show shimmer (respects prefers-reduced-motion)\r\n * - circle: renders as a circle (useful for avatars)\r\n * - className: pass-through for custom styling\r\n *\r\n * Accessibility:\r\n * - role=\"status\" with aria-busy on parent usage is recommended by developer.\r\n * - For individual skeletons that should be ignored by assistive tech set aria-hidden=true.\r\n */\r\n/**\r\n * Skeleton.tsx + Helper Components\r\n * --------------------------------\r\n * Adds:\r\n * - Skeleton.Text\r\n * - Skeleton.Paragraph\r\n * - Skeleton.Avatar\r\n * - Skeleton.Button\r\n * - Skeleton.Card\r\n */\r\n\r\nimport React from 'react';\r\nimport './Skeleton.css';\r\n\r\ntype Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n\r\ninterface SkeletonProps {\r\n size?: Size;\r\n width?: number | string;\r\n animated?: boolean;\r\n circle?: boolean;\r\n ariaHidden?: boolean;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n}\r\n\r\nfunction formatWidth(w?: number | string): string | undefined {\r\n if (w === undefined) return undefined;\r\n if (typeof w === 'number') return `${w}px`;\r\n return w;\r\n}\r\n\r\nexport function Skeleton({\r\n size = 'md',\r\n width,\r\n animated = true,\r\n circle = false,\r\n ariaHidden = true,\r\n className = '',\r\n style,\r\n}: SkeletonProps) {\r\n const classes = [\r\n 'ui-skeleton',\r\n `ui-skeleton--${size}`,\r\n animated && 'ui-skeleton--animated',\r\n circle && 'ui-skeleton--circle',\r\n className,\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <span\r\n className={classes}\r\n style={{\r\n width: formatWidth(width),\r\n display: width ? 'block' : undefined,\r\n ...style,\r\n }}\r\n aria-hidden={ariaHidden}\r\n role=\"presentation\"\r\n />\r\n );\r\n}\r\n\r\n/* -------------------------------------------------------\r\n * Helper Components\r\n * ------------------------------------------------------*/\r\n\r\n/**\r\n * Simple text line.\r\n * Default width: 100%\r\n */\r\nfunction Text({\r\n width = '100%',\r\n size = 'md',\r\n}: {\r\n width?: number | string;\r\n size?: Size;\r\n}) {\r\n return <Skeleton width={width} size={size} />;\r\n}\r\n\r\n/**\r\n * Paragraph: multiple lines of text skeleton\r\n * lines default = 3\r\n */\r\nfunction Paragraph({\r\n lines = 3,\r\n size = 'md',\r\n}: {\r\n lines?: number;\r\n size?: Size;\r\n}) {\r\n const arr = Array.from({ length: lines });\r\n\r\n return (\r\n <div aria-hidden=\"true\">\r\n {arr.map((_, i) => (\r\n <div key={i} style={{ marginBlockEnd: '8px' }}>\r\n <Skeleton width={i === lines - 1 ? '75%' : '100%'} size={size} />\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}\r\n\r\n/**\r\n * Avatar placeholder, circle with dynamic size\r\n */\r\nfunction AvatarSkeleton({ size = 40 }: { size?: number }) {\r\n return <Skeleton circle width={size} animated />;\r\n}\r\n\r\n/**\r\n * Button skeleton (pill-like shape)\r\n */\r\nfunction ButtonSkeleton({\r\n width = 100,\r\n height = 36,\r\n}: {\r\n width?: number;\r\n height?: number;\r\n}) {\r\n return (\r\n <Skeleton\r\n width={width}\r\n size=\"md\"\r\n animated\r\n style={{\r\n height,\r\n borderRadius: 'var(--radius-md)',\r\n }}\r\n />\r\n );\r\n}\r\n\r\n/**\r\n * Card Skeleton: header + lines + footer bar\r\n */\r\nfunction CardSkeleton() {\r\n return (\r\n <div aria-hidden=\"true\">\r\n <Skeleton width=\"60%\" size=\"lg\" style={{ marginBottom: 12 }} />\r\n <Paragraph lines={3} />\r\n <Skeleton width=\"40%\" size=\"md\" style={{ marginTop: 16 }} />\r\n </div>\r\n );\r\n}\r\n\r\n/* Attach helpers */\r\nSkeleton.Text = Text;\r\nSkeleton.Paragraph = Paragraph;\r\nSkeleton.Avatar = AvatarSkeleton;\r\nSkeleton.Button = ButtonSkeleton;\r\nSkeleton.Card = CardSkeleton;\r\n\r\nexport default Skeleton;\r\n"],"names":["formatWidth","w","Skeleton","size","width","animated","circle","ariaHidden","className","style","classes","jsx","Text","Paragraph","lines","arr","_","AvatarSkeleton","ButtonSkeleton","height","CardSkeleton","jsxs"],"mappings":";;AAyCA,SAASA,EAAYC,GAAyC;AAC5D,MAAIA,MAAM;AACV,WAAI,OAAOA,KAAM,WAAiB,GAAGA,CAAC,OAC/BA;AACT;AAEO,SAASC,EAAS;AAAA,EACvB,MAAAC,IAAO;AAAA,EACP,OAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,QAAAC,IAAS;AAAA,EACT,YAAAC,IAAa;AAAA,EACb,WAAAC,IAAY;AAAA,EACZ,OAAAC;AACF,GAAkB;AAChB,QAAMC,IAAU;AAAA,IACd;AAAA,IACA,gBAAgBP,CAAI;AAAA,IACpBE,KAAY;AAAA,IACZC,KAAU;AAAA,IACVE;AAAA,EAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE,gBAAAG;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWD;AAAA,MACX,OAAO;AAAA,QACL,OAAOV,EAAYI,CAAK;AAAA,QACxB,SAASA,IAAQ,UAAU;AAAA,QAC3B,GAAGK;AAAA,MAAA;AAAA,MAEL,eAAaF;AAAA,MACb,MAAK;AAAA,IAAA;AAAA,EAAA;AAGX;AAUA,SAASK,EAAK;AAAA,EACZ,OAAAR,IAAQ;AAAA,EACR,MAAAD,IAAO;AACT,GAGG;AACD,SAAO,gBAAAQ,EAACT,GAAA,EAAS,OAAAE,GAAc,MAAAD,EAAA,CAAY;AAC7C;AAMA,SAASU,EAAU;AAAA,EACjB,OAAAC,IAAQ;AAAA,EACR,MAAAX,IAAO;AACT,GAGG;AACD,QAAMY,IAAM,MAAM,KAAK,EAAE,QAAQD,GAAO;AAExC,SACE,gBAAAH,EAAC,OAAA,EAAI,eAAY,QACd,UAAAI,EAAI,IAAI,CAACC,GAAG,MACX,gBAAAL,EAAC,OAAA,EAAY,OAAO,EAAE,gBAAgB,MAAA,GACpC,UAAA,gBAAAA,EAACT,GAAA,EAAS,OAAO,MAAMY,IAAQ,IAAI,QAAQ,QAAQ,MAAAX,EAAA,CAAY,EAAA,GADvD,CAEV,CACD,GACH;AAEJ;AAKA,SAASc,EAAe,EAAE,MAAAd,IAAO,MAAyB;AACxD,2BAAQD,GAAA,EAAS,QAAM,IAAC,OAAOC,GAAM,UAAQ,IAAC;AAChD;AAKA,SAASe,EAAe;AAAA,EACtB,OAAAd,IAAQ;AAAA,EACR,QAAAe,IAAS;AACX,GAGG;AACD,SACE,gBAAAR;AAAA,IAACT;AAAA,IAAA;AAAA,MACC,OAAAE;AAAA,MACA,MAAK;AAAA,MACL,UAAQ;AAAA,MACR,OAAO;AAAA,QACL,QAAAe;AAAA,QACA,cAAc;AAAA,MAAA;AAAA,IAChB;AAAA,EAAA;AAGN;AAKA,SAASC,IAAe;AACtB,SACE,gBAAAC,EAAC,OAAA,EAAI,eAAY,QACf,UAAA;AAAA,IAAA,gBAAAV,EAACT,GAAA,EAAS,OAAM,OAAM,MAAK,MAAK,OAAO,EAAE,cAAc,GAAA,EAAG,CAAG;AAAA,IAC7D,gBAAAS,EAACE,GAAA,EAAU,OAAO,EAAA,CAAG;AAAA,IACrB,gBAAAF,EAACT,GAAA,EAAS,OAAM,OAAM,MAAK,MAAK,OAAO,EAAE,WAAW,KAAG,CAAG;AAAA,EAAA,GAC5D;AAEJ;AAGAA,EAAS,OAAOU;AAChBV,EAAS,YAAYW;AACrBX,EAAS,SAASe;AAClBf,EAAS,SAASgB;AAClBhB,EAAS,OAAOkB;"}
|