@kobalte/core
Version:
Unstyled components and primitives for building accessible web apps and design systems with SolidJS.
539 lines (526 loc) • 14.9 kB
JSX
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
};