UNPKG

@kobalte/core

Version:

Unstyled components and primitives for building accessible web apps and design systems with SolidJS.

539 lines (526 loc) 14.9 kB
import { TabsKeyboardDelegate } from "./XTJD7L6B.jsx"; import { createListState, createSelectableCollection, createSelectableItem } from "./QZDH5R5B.jsx"; import { createDomCollection, createDomCollectionItem } from "./SOM3K36D.jsx"; import { useLocale } from "./LR7LBJN3.jsx"; import { createControllableSignal } from "./FN6EICGO.jsx"; import { Polymorphic } from "./FLVHQV4A.jsx"; import { __export } from "./5WXHJDCZ.jsx"; // src/tabs/index.tsx var tabs_exports = {}; __export(tabs_exports, { Content: () => TabsContent, Indicator: () => TabsIndicator, List: () => TabsList, Root: () => TabsRoot, Tabs: () => Tabs, Trigger: () => TabsTrigger, useTabsContext: () => useTabsContext }); // src/tabs/tabs-content.tsx import { getFocusableTreeWalker, mergeRefs } from "@kobalte/utils"; import { Show, createEffect, createSignal, on, onCleanup, splitProps } from "solid-js"; import createPresence from "solid-presence"; // src/tabs/tabs-context.tsx import { createContext, useContext } from "solid-js"; var TabsContext = createContext(); function useTabsContext() { const context = useContext(TabsContext); if (context === void 0) { throw new Error( "[kobalte]: `useTabsContext` must be used within a `Tabs` component" ); } return context; } // src/tabs/tabs-content.tsx function TabsContent(props) { const [ref, setRef] = createSignal(); const context = useTabsContext(); const [local, others] = splitProps(props, [ "ref", "id", "value", "forceMount" ]); const [tabIndex, setTabIndex] = createSignal(0); const id = () => local.id ?? context.generateContentId(local.value); const isSelected = () => context.listState().selectedKey() === local.value; const { present } = createPresence({ show: () => local.forceMount || isSelected(), element: () => ref() ?? null }); createEffect( on([() => ref(), () => present()], ([ref2, isPresent]) => { if (ref2 == null || !isPresent) { return; } const updateTabIndex = () => { const walker = getFocusableTreeWalker(ref2, { tabbable: true }); setTabIndex(walker.nextNode() ? void 0 : 0); }; updateTabIndex(); const observer = new MutationObserver(updateTabIndex); observer.observe(ref2, { subtree: true, childList: true, attributes: true, attributeFilter: ["tabindex", "disabled"] }); onCleanup(() => { observer.disconnect(); }); }) ); createEffect( on([() => local.value, id], ([value, id2]) => { context.contentIdsMap().set(value, id2); }) ); return <Show when={present()}><Polymorphic as="div" ref={mergeRefs(setRef, local.ref)} id={id()} role="tabpanel" tabIndex={tabIndex()} aria-labelledby={context.triggerIdsMap().get(local.value)} data-orientation={context.orientation()} data-selected={isSelected() ? "" : void 0} {...others} /></Show>; } // src/tabs/tabs-indicator.tsx import { createEffect as createEffect2, createSignal as createSignal2, on as on2, onMount, splitProps as splitProps2 } from "solid-js"; import { combineStyle } from "@solid-primitives/props"; import { createResizeObserver } from "@solid-primitives/resize-observer"; function TabsIndicator(props) { const context = useTabsContext(); const [local, others] = splitProps2(props, ["style"]); const [style, setStyle] = createSignal2({ width: void 0, height: void 0 }); const { direction } = useLocale(); const computeStyle = () => { const selectedTab = context.selectedTab(); if (selectedTab == null) { return; } const styleObj = { transform: void 0, width: void 0, height: void 0 }; const offset = direction() === "rtl" ? -1 * (selectedTab.offsetParent?.offsetWidth - selectedTab.offsetWidth - selectedTab.offsetLeft) : selectedTab.offsetLeft; styleObj.transform = context.orientation() === "vertical" ? `translateY(${selectedTab.offsetTop}px)` : `translateX(${offset}px)`; if (context.orientation() === "horizontal") { styleObj.width = `${selectedTab.offsetWidth}px`; } else { styleObj.height = `${selectedTab.offsetHeight}px`; } setStyle(styleObj); }; onMount(() => { queueMicrotask(() => { computeStyle(); }); }); createEffect2( on2( [context.selectedTab, context.orientation, direction], () => { computeStyle(); }, { defer: true } ) ); const [resizing, setResizing] = createSignal2(false); let timeout = null; let prevTarget = null; createResizeObserver(context.selectedTab, (_, t) => { if (prevTarget !== t) { prevTarget = t; return; } setResizing(true); if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { timeout = null; setResizing(false); }, 1); computeStyle(); }); return <Polymorphic as="div" role="presentation" style={combineStyle(style(), local.style)} data-orientation={context.orientation()} data-resizing={resizing()} {...others} />; } // src/tabs/tabs-list.tsx import { composeEventHandlers, mergeRefs as mergeRefs2 } from "@kobalte/utils"; import { createEffect as createEffect3, splitProps as splitProps3 } from "solid-js"; function TabsList(props) { let ref; const context = useTabsContext(); const [local, others] = splitProps3(props, [ "ref", "onKeyDown", "onMouseDown", "onFocusIn", "onFocusOut" ]); const { direction } = useLocale(); const delegate = new TabsKeyboardDelegate( () => context.listState().collection(), direction, context.orientation ); const selectableCollection = createSelectableCollection( { selectionManager: () => context.listState().selectionManager(), keyboardDelegate: () => delegate, selectOnFocus: () => context.activationMode() === "automatic", shouldFocusWrap: false, // handled by the keyboard delegate disallowEmptySelection: true }, () => ref ); createEffect3(() => { if (ref == null) { return; } const selectedTab = ref.querySelector( `[data-key="${context.listState().selectedKey()}"]` ); if (selectedTab != null) { context.setSelectedTab(selectedTab); } }); return <Polymorphic as="div" ref={mergeRefs2((el) => ref = el, local.ref)} role="tablist" aria-orientation={context.orientation()} data-orientation={context.orientation()} onKeyDown={composeEventHandlers([ local.onKeyDown, selectableCollection.onKeyDown ])} onMouseDown={composeEventHandlers([ local.onMouseDown, selectableCollection.onMouseDown ])} onFocusIn={composeEventHandlers([ local.onFocusIn, selectableCollection.onFocusIn ])} onFocusOut={composeEventHandlers([ local.onFocusOut, selectableCollection.onFocusOut ])} {...others} />; } // src/tabs/tabs-root.tsx import { mergeDefaultProps } from "@kobalte/utils"; import { createEffect as createEffect4, createSignal as createSignal3, createUniqueId, on as on3, splitProps as splitProps5 } from "solid-js"; // src/list/create-single-select-list-state.ts import { access } from "@kobalte/utils"; import { createMemo, mergeProps, splitProps as splitProps4 } from "solid-js"; function createSingleSelectListState(props) { const [selectedKey, setSelectedKey] = createControllableSignal({ value: () => access(props.selectedKey), defaultValue: () => access(props.defaultSelectedKey), onChange: (value) => props.onSelectionChange?.(value) }); const selectedKeys = createMemo(() => { const selection = selectedKey(); return selection != null ? [selection] : []; }); const [, defaultCreateListStateProps] = splitProps4(props, [ "onSelectionChange" ]); const createListStateProps = mergeProps(defaultCreateListStateProps, { selectionMode: "single", disallowEmptySelection: true, allowDuplicateSelectionEvents: true, selectedKeys, onSelectionChange: (keys) => { const key = keys.values().next().value; if (key === selectedKey()) { props.onSelectionChange?.(key); } setSelectedKey(key); } }); const { collection, selectionManager } = createListState(createListStateProps); const selectedItem = createMemo(() => { const selection = selectedKey(); return selection != null ? collection().getItem(selection) : void 0; }); return { collection, selectionManager, selectedKey, setSelectedKey, selectedItem }; } // src/tabs/tabs-root.tsx function TabsRoot(props) { const defaultId = `tabs-${createUniqueId()}`; const mergedProps = mergeDefaultProps( { id: defaultId, orientation: "horizontal", activationMode: "automatic" }, props ); const [local, others] = splitProps5(mergedProps, [ "value", "defaultValue", "onChange", "orientation", "activationMode", "disabled" ]); const [items, setItems] = createSignal3([]); const [selectedTab, setSelectedTab] = createSignal3(); const { DomCollectionProvider } = createDomCollection({ items, onItemsChange: setItems }); const listState = createSingleSelectListState({ selectedKey: () => local.value, defaultSelectedKey: () => local.defaultValue, onSelectionChange: (key) => local.onChange?.(String(key)), dataSource: items }); let lastSelectedKey = listState.selectedKey(); createEffect4( on3( [ () => listState.selectionManager(), () => listState.collection(), () => listState.selectedKey() ], ([selectionManager, collection, currentSelectedKey]) => { let selectedKey = currentSelectedKey; if (selectionManager.isEmpty() || selectedKey == null || !collection.getItem(selectedKey)) { selectedKey = collection.getFirstKey(); let selectedItem = selectedKey != null ? collection.getItem(selectedKey) : void 0; while (selectedItem?.disabled && selectedItem.key !== collection.getLastKey()) { selectedKey = collection.getKeyAfter(selectedItem.key); selectedItem = selectedKey != null ? collection.getItem(selectedKey) : void 0; } if (selectedItem?.disabled && selectedKey === collection.getLastKey()) { selectedKey = collection.getFirstKey(); } if (selectedKey != null) { selectionManager.setSelectedKeys([selectedKey]); } } if (selectionManager.focusedKey() == null || !selectionManager.isFocused() && selectedKey !== lastSelectedKey) { selectionManager.setFocusedKey(selectedKey); } lastSelectedKey = selectedKey; } ) ); const triggerIdsMap = /* @__PURE__ */ new Map(); const contentIdsMap = /* @__PURE__ */ new Map(); const context = { isDisabled: () => local.disabled ?? false, orientation: () => local.orientation, activationMode: () => local.activationMode, triggerIdsMap: () => triggerIdsMap, contentIdsMap: () => contentIdsMap, listState: () => listState, selectedTab, setSelectedTab, generateTriggerId: (value) => `${others.id}-trigger-${value}`, generateContentId: (value) => `${others.id}-content-${value}` }; return <DomCollectionProvider><TabsContext.Provider value={context}><Polymorphic as="div" data-orientation={context.orientation()} {...others} /></TabsContext.Provider></DomCollectionProvider>; } // src/tabs/tabs-trigger.tsx import { composeEventHandlers as composeEventHandlers2, focusWithoutScrolling, isWebKit, mergeDefaultProps as mergeDefaultProps2, mergeRefs as mergeRefs3 } from "@kobalte/utils"; import { createEffect as createEffect5, on as on4, splitProps as splitProps6 } from "solid-js"; function TabsTrigger(props) { let ref; const context = useTabsContext(); const mergedProps = mergeDefaultProps2( { type: "button" }, props ); const [local, others] = splitProps6(mergedProps, [ "ref", "id", "value", "disabled", "onPointerDown", "onPointerUp", "onClick", "onKeyDown", "onMouseDown", "onFocus" ]); const id = () => local.id ?? context.generateTriggerId(local.value); const isHighlighted = () => context.listState().selectionManager().focusedKey() === local.value; const isDisabled = () => local.disabled || context.isDisabled(); const contentId = () => context.contentIdsMap().get(local.value); createDomCollectionItem({ getItem: () => ({ ref: () => ref, type: "item", key: local.value, textValue: "", // not applicable here disabled: isDisabled() }) }); const selectableItem = createSelectableItem( { key: () => local.value, selectionManager: () => context.listState().selectionManager(), disabled: isDisabled }, () => ref ); const onClick = (e) => { if (isWebKit()) { focusWithoutScrolling(e.currentTarget); } }; createEffect5( on4([() => local.value, id], ([value, id2]) => { context.triggerIdsMap().set(value, id2); }) ); return <Polymorphic as="button" ref={mergeRefs3((el) => ref = el, local.ref)} id={id()} role="tab" tabIndex={!isDisabled() ? selectableItem.tabIndex() : void 0} disabled={isDisabled()} aria-selected={selectableItem.isSelected()} aria-disabled={isDisabled() || void 0} aria-controls={selectableItem.isSelected() ? contentId() : void 0} data-key={selectableItem.dataKey()} data-orientation={context.orientation()} data-selected={selectableItem.isSelected() ? "" : void 0} data-highlighted={isHighlighted() ? "" : void 0} data-disabled={isDisabled() ? "" : void 0} onPointerDown={composeEventHandlers2([ local.onPointerDown, selectableItem.onPointerDown ])} onPointerUp={composeEventHandlers2([ local.onPointerUp, selectableItem.onPointerUp ])} onClick={composeEventHandlers2([ local.onClick, selectableItem.onClick, onClick ])} onKeyDown={composeEventHandlers2([ local.onKeyDown, selectableItem.onKeyDown ])} onMouseDown={composeEventHandlers2([ local.onMouseDown, selectableItem.onMouseDown ])} onFocus={composeEventHandlers2([local.onFocus, selectableItem.onFocus])} {...others} />; } // src/tabs/index.tsx var Tabs = Object.assign(TabsRoot, { Content: TabsContent, Indicator: TabsIndicator, List: TabsList, Trigger: TabsTrigger }); export { createSingleSelectListState, useTabsContext, TabsContent, TabsIndicator, TabsList, TabsRoot, TabsTrigger, Tabs, tabs_exports };