@kobalte/core
Version:
Unstyled components and primitives for building accessible web apps and design systems with SolidJS.
1,058 lines (1,045 loc) • 30.6 kB
JSX
import {
createCollection
} from "./YRH543JR.jsx";
import {
useLocale
} from "./LR7LBJN3.jsx";
import {
createControllableSignal
} from "./FN6EICGO.jsx";
// src/selection/types.ts
var Selection = class _Selection extends Set {
anchorKey;
currentKey;
constructor(keys, anchorKey, currentKey) {
super(keys);
if (keys instanceof _Selection) {
this.anchorKey = anchorKey || keys.anchorKey;
this.currentKey = currentKey || keys.currentKey;
} else {
this.anchorKey = anchorKey;
this.currentKey = currentKey;
}
}
};
// src/selection/create-multiple-selection-state.ts
import { access, mergeDefaultProps } from "@kobalte/utils";
import { createEffect, createMemo, createSignal } from "solid-js";
// src/selection/create-controllable-selection-signal.ts
function createControllableSelectionSignal(props) {
const [_value, setValue] = createControllableSignal(props);
const value = () => _value() ?? new Selection();
return [value, setValue];
}
// src/selection/utils.ts
import { isAppleDevice, isMac } from "@kobalte/utils";
function isNonContiguousSelectionModifier(e) {
return isAppleDevice() ? e.altKey : e.ctrlKey;
}
function isCtrlKeyPressed(e) {
if (isMac()) {
return e.metaKey;
}
return e.ctrlKey;
}
function convertSelection(selection) {
return new Selection(selection);
}
function isSameSelection(setA, setB) {
if (setA.size !== setB.size) {
return false;
}
for (const item of setA) {
if (!setB.has(item)) {
return false;
}
}
return true;
}
// src/selection/create-multiple-selection-state.ts
function createMultipleSelectionState(props) {
const mergedProps = mergeDefaultProps(
{
selectionMode: "none",
selectionBehavior: "toggle"
},
props
);
const [isFocused, setFocused] = createSignal(false);
const [focusedKey, setFocusedKey] = createSignal();
const selectedKeysProp = createMemo(() => {
const selection = access(mergedProps.selectedKeys);
if (selection != null) {
return convertSelection(selection);
}
return selection;
});
const defaultSelectedKeys = createMemo(() => {
const defaultSelection = access(mergedProps.defaultSelectedKeys);
if (defaultSelection != null) {
return convertSelection(defaultSelection);
}
return new Selection();
});
const [selectedKeys, _setSelectedKeys] = createControllableSelectionSignal({
value: selectedKeysProp,
defaultValue: defaultSelectedKeys,
onChange: (value) => mergedProps.onSelectionChange?.(value)
});
const [selectionBehavior, setSelectionBehavior] = createSignal(access(mergedProps.selectionBehavior));
const selectionMode = () => access(mergedProps.selectionMode);
const disallowEmptySelection = () => access(mergedProps.disallowEmptySelection) ?? false;
const setSelectedKeys = (keys) => {
if (access(mergedProps.allowDuplicateSelectionEvents) || !isSameSelection(keys, selectedKeys())) {
_setSelectedKeys(keys);
}
};
createEffect(() => {
const selection = selectedKeys();
if (access(mergedProps.selectionBehavior) === "replace" && selectionBehavior() === "toggle" && typeof selection === "object" && selection.size === 0) {
setSelectionBehavior("replace");
}
});
createEffect(() => {
setSelectionBehavior(access(mergedProps.selectionBehavior) ?? "toggle");
});
return {
selectionMode,
disallowEmptySelection,
selectionBehavior,
setSelectionBehavior,
isFocused,
setFocused,
focusedKey,
setFocusedKey,
selectedKeys,
setSelectedKeys
};
}
// src/selection/create-type-select.ts
import { access as access2 } from "@kobalte/utils";
import { createSignal as createSignal2 } from "solid-js";
function createTypeSelect(props) {
const [search, setSearch] = createSignal2("");
const [timeoutId, setTimeoutId] = createSignal2(-1);
const onKeyDown = (e) => {
if (access2(props.isDisabled)) {
return;
}
const delegate = access2(props.keyboardDelegate);
const manager = access2(props.selectionManager);
if (!delegate.getKeyForSearch) {
return;
}
const character = getStringForKey(e.key);
if (!character || e.ctrlKey || e.metaKey) {
return;
}
if (character === " " && search().trim().length > 0) {
e.preventDefault();
e.stopPropagation();
}
let newSearch = setSearch((prev) => prev + character);
let key = delegate.getKeyForSearch(newSearch, manager.focusedKey()) ?? delegate.getKeyForSearch(newSearch);
if (key == null && isAllSameLetter(newSearch)) {
newSearch = newSearch[0];
key = delegate.getKeyForSearch(newSearch, manager.focusedKey()) ?? delegate.getKeyForSearch(newSearch);
}
if (key != null) {
manager.setFocusedKey(key);
props.onTypeSelect?.(key);
}
clearTimeout(timeoutId());
setTimeoutId(window.setTimeout(() => setSearch(""), 500));
};
return {
typeSelectHandlers: {
onKeyDown
}
};
}
function getStringForKey(key) {
if (key.length === 1 || !/^[A-Z]/i.test(key)) {
return key;
}
return "";
}
function isAllSameLetter(search) {
return search.split("").every((letter) => letter === search[0]);
}
// src/selection/create-selectable-collection.ts
import {
access as access3,
callHandler,
createEventListener,
focusWithoutScrolling,
getFocusableTreeWalker,
scrollIntoView
} from "@kobalte/utils";
import {
createEffect as createEffect2,
createMemo as createMemo2,
mergeProps,
on,
onMount
} from "solid-js";
function createSelectableCollection(props, ref, scrollRef) {
const defaultProps = {
selectOnFocus: () => access3(props.selectionManager).selectionBehavior() === "replace"
};
const mergedProps = mergeProps(defaultProps, props);
const finalScrollRef = () => scrollRef?.() ?? ref();
const { direction } = useLocale();
let scrollPos = { top: 0, left: 0 };
createEventListener(
() => !access3(mergedProps.isVirtualized) ? finalScrollRef() : void 0,
"scroll",
() => {
const scrollEl = finalScrollRef();
if (!scrollEl) {
return;
}
scrollPos = {
top: scrollEl.scrollTop,
left: scrollEl.scrollLeft
};
}
);
const { typeSelectHandlers } = createTypeSelect({
isDisabled: () => access3(mergedProps.disallowTypeAhead),
keyboardDelegate: () => access3(mergedProps.keyboardDelegate),
selectionManager: () => access3(mergedProps.selectionManager)
});
const orientation = () => access3(mergedProps.orientation) ?? "vertical";
const onKeyDown = (e) => {
callHandler(e, typeSelectHandlers.onKeyDown);
if (e.altKey && e.key === "Tab") {
e.preventDefault();
}
const refEl = ref();
if (!refEl?.contains(e.target)) {
return;
}
const manager = access3(mergedProps.selectionManager);
const selectOnFocus = access3(mergedProps.selectOnFocus);
const navigateToKey = (key) => {
if (key != null) {
manager.setFocusedKey(key);
if (e.shiftKey && manager.selectionMode() === "multiple") {
manager.extendSelection(key);
} else if (selectOnFocus && !isNonContiguousSelectionModifier(e)) {
manager.replaceSelection(key);
}
}
};
const delegate = access3(mergedProps.keyboardDelegate);
const shouldFocusWrap = access3(mergedProps.shouldFocusWrap);
const focusedKey = manager.focusedKey();
switch (e.key) {
case (orientation() === "vertical" ? "ArrowDown" : "ArrowRight"): {
if (delegate.getKeyBelow) {
e.preventDefault();
let nextKey;
if (focusedKey != null) {
nextKey = delegate.getKeyBelow(focusedKey);
} else {
nextKey = delegate.getFirstKey?.();
}
if (nextKey == null && shouldFocusWrap) {
nextKey = delegate.getFirstKey?.(focusedKey);
}
navigateToKey(nextKey);
}
break;
}
case (orientation() === "vertical" ? "ArrowUp" : "ArrowLeft"): {
if (delegate.getKeyAbove) {
e.preventDefault();
let nextKey;
if (focusedKey != null) {
nextKey = delegate.getKeyAbove(focusedKey);
} else {
nextKey = delegate.getLastKey?.();
}
if (nextKey == null && shouldFocusWrap) {
nextKey = delegate.getLastKey?.(focusedKey);
}
navigateToKey(nextKey);
}
break;
}
case (orientation() === "vertical" ? "ArrowLeft" : "ArrowUp"): {
if (delegate.getKeyLeftOf) {
e.preventDefault();
const isRTL = direction() === "rtl";
let nextKey;
if (focusedKey != null) {
nextKey = delegate.getKeyLeftOf(focusedKey);
} else {
nextKey = isRTL ? delegate.getFirstKey?.() : delegate.getLastKey?.();
}
navigateToKey(nextKey);
}
break;
}
case (orientation() === "vertical" ? "ArrowRight" : "ArrowDown"): {
if (delegate.getKeyRightOf) {
e.preventDefault();
const isRTL = direction() === "rtl";
let nextKey;
if (focusedKey != null) {
nextKey = delegate.getKeyRightOf(focusedKey);
} else {
nextKey = isRTL ? delegate.getLastKey?.() : delegate.getFirstKey?.();
}
navigateToKey(nextKey);
}
break;
}
case "Home":
if (delegate.getFirstKey) {
e.preventDefault();
const firstKey = delegate.getFirstKey(
focusedKey,
isCtrlKeyPressed(e)
);
if (firstKey != null) {
manager.setFocusedKey(firstKey);
if (isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode() === "multiple") {
manager.extendSelection(firstKey);
} else if (selectOnFocus) {
manager.replaceSelection(firstKey);
}
}
}
break;
case "End":
if (delegate.getLastKey) {
e.preventDefault();
const lastKey = delegate.getLastKey(focusedKey, isCtrlKeyPressed(e));
if (lastKey != null) {
manager.setFocusedKey(lastKey);
if (isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode() === "multiple") {
manager.extendSelection(lastKey);
} else if (selectOnFocus) {
manager.replaceSelection(lastKey);
}
}
}
break;
case "PageDown":
if (delegate.getKeyPageBelow && focusedKey != null) {
e.preventDefault();
const nextKey = delegate.getKeyPageBelow(focusedKey);
navigateToKey(nextKey);
}
break;
case "PageUp":
if (delegate.getKeyPageAbove && focusedKey != null) {
e.preventDefault();
const nextKey = delegate.getKeyPageAbove(focusedKey);
navigateToKey(nextKey);
}
break;
case "a":
if (isCtrlKeyPressed(e) && manager.selectionMode() === "multiple" && access3(mergedProps.disallowSelectAll) !== true) {
e.preventDefault();
manager.selectAll();
}
break;
case "Escape":
if (!e.defaultPrevented) {
e.preventDefault();
if (!access3(mergedProps.disallowEmptySelection)) {
manager.clearSelection();
}
}
break;
case "Tab": {
if (!access3(mergedProps.allowsTabNavigation)) {
if (e.shiftKey) {
refEl.focus();
} else {
const walker = getFocusableTreeWalker(refEl, { tabbable: true });
let next;
let last;
do {
last = walker.lastChild();
if (last) {
next = last;
}
} while (last);
if (next && !next.contains(document.activeElement)) {
focusWithoutScrolling(next);
}
}
break;
}
}
}
};
const onFocusIn = (e) => {
const manager = access3(mergedProps.selectionManager);
const delegate = access3(mergedProps.keyboardDelegate);
const selectOnFocus = access3(mergedProps.selectOnFocus);
if (manager.isFocused()) {
if (!e.currentTarget.contains(e.target)) {
manager.setFocused(false);
}
return;
}
if (!e.currentTarget.contains(e.target)) {
return;
}
manager.setFocused(true);
if (manager.focusedKey() == null) {
const navigateToFirstKey = (key) => {
if (key == null) {
return;
}
manager.setFocusedKey(key);
if (selectOnFocus) {
manager.replaceSelection(key);
}
};
const relatedTarget = e.relatedTarget;
if (relatedTarget && e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING) {
navigateToFirstKey(
manager.lastSelectedKey() ?? delegate.getLastKey?.()
);
} else {
navigateToFirstKey(
manager.firstSelectedKey() ?? delegate.getFirstKey?.()
);
}
} else if (!access3(mergedProps.isVirtualized)) {
const scrollEl = finalScrollRef();
if (scrollEl) {
scrollEl.scrollTop = scrollPos.top;
scrollEl.scrollLeft = scrollPos.left;
const element = scrollEl.querySelector(
`[data-key="${manager.focusedKey()}"]`
);
if (element) {
focusWithoutScrolling(element);
scrollIntoView(scrollEl, element);
}
}
}
};
const onFocusOut = (e) => {
const manager = access3(mergedProps.selectionManager);
if (!e.currentTarget.contains(e.relatedTarget)) {
manager.setFocused(false);
}
};
const onMouseDown = (e) => {
if (finalScrollRef() === e.target) {
e.preventDefault();
}
};
const tryAutoFocus = () => {
const autoFocus = access3(mergedProps.autoFocus);
if (!autoFocus) {
return;
}
const manager = access3(mergedProps.selectionManager);
const delegate = access3(mergedProps.keyboardDelegate);
let focusedKey;
if (autoFocus === "first") {
focusedKey = delegate.getFirstKey?.();
}
if (autoFocus === "last") {
focusedKey = delegate.getLastKey?.();
}
const selectedKeys = manager.selectedKeys();
if (selectedKeys.size) {
focusedKey = selectedKeys.values().next().value;
}
manager.setFocused(true);
manager.setFocusedKey(focusedKey);
const refEl = ref();
if (refEl && focusedKey == null && !access3(mergedProps.shouldUseVirtualFocus)) {
focusWithoutScrolling(refEl);
}
};
onMount(() => {
if (mergedProps.deferAutoFocus) {
setTimeout(tryAutoFocus, 0);
} else {
tryAutoFocus();
}
});
createEffect2(
on(
[
finalScrollRef,
() => access3(mergedProps.isVirtualized),
() => access3(mergedProps.selectionManager).focusedKey()
],
(newValue) => {
const [scrollEl, isVirtualized, focusedKey] = newValue;
if (isVirtualized) {
focusedKey && mergedProps.scrollToKey?.(focusedKey);
} else {
if (focusedKey && scrollEl) {
const element = scrollEl.querySelector(
`[data-key="${focusedKey}"]`
);
if (element) {
scrollIntoView(scrollEl, element);
}
}
}
}
)
);
const tabIndex = createMemo2(() => {
if (access3(mergedProps.shouldUseVirtualFocus)) {
return void 0;
}
return access3(mergedProps.selectionManager).focusedKey() == null ? 0 : -1;
});
return {
tabIndex,
onKeyDown,
onMouseDown,
onFocusIn,
onFocusOut
};
}
// src/selection/create-selectable-item.ts
import {
access as access4,
focusWithoutScrolling as focusWithoutScrolling2
} from "@kobalte/utils";
import {
createEffect as createEffect3,
createMemo as createMemo3,
on as on2
} from "solid-js";
function createSelectableItem(props, ref) {
const manager = () => access4(props.selectionManager);
const key = () => access4(props.key);
const shouldUseVirtualFocus = () => access4(props.shouldUseVirtualFocus);
const onSelect = (e) => {
if (manager().selectionMode() === "none") {
return;
}
if (manager().selectionMode() === "single") {
if (manager().isSelected(key()) && !manager().disallowEmptySelection()) {
manager().toggleSelection(key());
} else {
manager().replaceSelection(key());
}
} else if (e?.shiftKey) {
manager().extendSelection(key());
} else if (manager().selectionBehavior() === "toggle" || isCtrlKeyPressed(e) || "pointerType" in e && e.pointerType === "touch") {
manager().toggleSelection(key());
} else {
manager().replaceSelection(key());
}
};
const isSelected = () => manager().isSelected(key());
const isDisabled = () => access4(props.disabled) || manager().isDisabled(key());
const allowsSelection = () => !isDisabled() && manager().canSelectItem(key());
let pointerDownType = null;
const onPointerDown = (e) => {
if (!allowsSelection()) {
return;
}
pointerDownType = e.pointerType;
if (e.pointerType === "mouse" && e.button === 0 && !access4(props.shouldSelectOnPressUp)) {
onSelect(e);
}
};
const onPointerUp = (e) => {
if (!allowsSelection()) {
return;
}
if (e.pointerType === "mouse" && e.button === 0 && access4(props.shouldSelectOnPressUp) && access4(props.allowsDifferentPressOrigin)) {
onSelect(e);
}
};
const onClick = (e) => {
if (!allowsSelection()) {
return;
}
if (access4(props.shouldSelectOnPressUp) && !access4(props.allowsDifferentPressOrigin) || pointerDownType !== "mouse") {
onSelect(e);
}
};
const onKeyDown = (e) => {
if (!allowsSelection() || !["Enter", " "].includes(e.key)) {
return;
}
if (isNonContiguousSelectionModifier(e)) {
manager().toggleSelection(key());
} else {
onSelect(e);
}
};
const onMouseDown = (e) => {
if (isDisabled()) {
e.preventDefault();
}
};
const onFocus = (e) => {
const refEl = ref();
if (shouldUseVirtualFocus() || isDisabled() || !refEl) {
return;
}
if (e.target === refEl) {
manager().setFocusedKey(key());
}
};
const tabIndex = createMemo3(() => {
if (shouldUseVirtualFocus() || isDisabled()) {
return void 0;
}
return key() === manager().focusedKey() ? 0 : -1;
});
const dataKey = createMemo3(() => {
return access4(props.virtualized) ? void 0 : key();
});
createEffect3(
on2(
[
ref,
key,
shouldUseVirtualFocus,
() => manager().focusedKey(),
() => manager().isFocused()
],
([refEl, key2, shouldUseVirtualFocus2, focusedKey, isFocused]) => {
if (refEl && key2 === focusedKey && isFocused && !shouldUseVirtualFocus2 && document.activeElement !== refEl) {
if (props.focus) {
props.focus();
} else {
focusWithoutScrolling2(refEl);
}
}
}
)
);
return {
isSelected,
isDisabled,
allowsSelection,
tabIndex,
dataKey,
onPointerDown,
onPointerUp,
onClick,
onKeyDown,
onMouseDown,
onFocus
};
}
// src/selection/selection-manager.ts
var SelectionManager = class {
collection;
state;
constructor(collection, state) {
this.collection = collection;
this.state = state;
}
/** The type of selection that is allowed in the collection. */
selectionMode() {
return this.state.selectionMode();
}
/** Whether the collection allows empty selection. */
disallowEmptySelection() {
return this.state.disallowEmptySelection();
}
/** The selection behavior for the collection. */
selectionBehavior() {
return this.state.selectionBehavior();
}
/** Sets the selection behavior for the collection. */
setSelectionBehavior(selectionBehavior) {
this.state.setSelectionBehavior(selectionBehavior);
}
/** Whether the collection is currently focused. */
isFocused() {
return this.state.isFocused();
}
/** Sets whether the collection is focused. */
setFocused(isFocused) {
this.state.setFocused(isFocused);
}
/** The current focused key in the collection. */
focusedKey() {
return this.state.focusedKey();
}
/** Sets the focused key. */
setFocusedKey(key) {
if (key == null || this.collection().getItem(key)) {
this.state.setFocusedKey(key);
}
}
/** The currently selected keys in the collection. */
selectedKeys() {
return this.state.selectedKeys();
}
/** Returns whether a key is selected. */
isSelected(key) {
if (this.state.selectionMode() === "none") {
return false;
}
const retrievedKey = this.getKey(key);
if (retrievedKey == null) {
return false;
}
return this.state.selectedKeys().has(retrievedKey);
}
/** Whether the selection is empty. */
isEmpty() {
return this.state.selectedKeys().size === 0;
}
/** Whether all items in the collection are selected. */
isSelectAll() {
if (this.isEmpty()) {
return false;
}
const selectedKeys = this.state.selectedKeys();
return this.getAllSelectableKeys().every((k) => selectedKeys.has(k));
}
firstSelectedKey() {
let first;
for (const key of this.state.selectedKeys()) {
const item = this.collection().getItem(key);
const isItemBeforeFirst = item?.index != null && first?.index != null && item.index < first.index;
if (!first || isItemBeforeFirst) {
first = item;
}
}
return first?.key;
}
lastSelectedKey() {
let last;
for (const key of this.state.selectedKeys()) {
const item = this.collection().getItem(key);
const isItemAfterLast = item?.index != null && last?.index != null && item.index > last.index;
if (!last || isItemAfterLast) {
last = item;
}
}
return last?.key;
}
/** Extends the selection to the given key. */
extendSelection(toKey) {
if (this.selectionMode() === "none") {
return;
}
if (this.selectionMode() === "single") {
this.replaceSelection(toKey);
return;
}
const retrievedToKey = this.getKey(toKey);
if (retrievedToKey == null) {
return;
}
const selectedKeys = this.state.selectedKeys();
const anchorKey = selectedKeys.anchorKey || retrievedToKey;
const selection = new Selection(selectedKeys, anchorKey, retrievedToKey);
for (const key of this.getKeyRange(
anchorKey,
selectedKeys.currentKey || retrievedToKey
)) {
selection.delete(key);
}
for (const key of this.getKeyRange(retrievedToKey, anchorKey)) {
if (this.canSelectItem(key)) {
selection.add(key);
}
}
this.state.setSelectedKeys(selection);
}
getKeyRange(from, to) {
const fromItem = this.collection().getItem(from);
const toItem = this.collection().getItem(to);
if (fromItem && toItem) {
if (fromItem.index != null && toItem.index != null && fromItem.index <= toItem.index) {
return this.getKeyRangeInternal(from, to);
}
return this.getKeyRangeInternal(to, from);
}
return [];
}
getKeyRangeInternal(from, to) {
const keys = [];
let key = from;
while (key != null) {
const item = this.collection().getItem(key);
if (item && item.type === "item") {
keys.push(key);
}
if (key === to) {
return keys;
}
key = this.collection().getKeyAfter(key);
}
return [];
}
getKey(key) {
const item = this.collection().getItem(key);
if (!item) {
return key;
}
if (!item || item.type !== "item") {
return null;
}
return item.key;
}
/** Toggles whether the given key is selected. */
toggleSelection(key) {
if (this.selectionMode() === "none") {
return;
}
if (this.selectionMode() === "single" && !this.isSelected(key)) {
this.replaceSelection(key);
return;
}
const retrievedKey = this.getKey(key);
if (retrievedKey == null) {
return;
}
const keys = new Selection(this.state.selectedKeys());
if (keys.has(retrievedKey)) {
keys.delete(retrievedKey);
} else if (this.canSelectItem(retrievedKey)) {
keys.add(retrievedKey);
keys.anchorKey = retrievedKey;
keys.currentKey = retrievedKey;
}
if (this.disallowEmptySelection() && keys.size === 0) {
return;
}
this.state.setSelectedKeys(keys);
}
/** Replaces the selection with only the given key. */
replaceSelection(key) {
if (this.selectionMode() === "none") {
return;
}
const retrievedKey = this.getKey(key);
if (retrievedKey == null) {
return;
}
const selection = this.canSelectItem(retrievedKey) ? new Selection([retrievedKey], retrievedKey, retrievedKey) : new Selection();
this.state.setSelectedKeys(selection);
}
/** Replaces the selection with the given keys. */
setSelectedKeys(keys) {
if (this.selectionMode() === "none") {
return;
}
const selection = new Selection();
for (const key of keys) {
const retrievedKey = this.getKey(key);
if (retrievedKey != null) {
selection.add(retrievedKey);
if (this.selectionMode() === "single") {
break;
}
}
}
this.state.setSelectedKeys(selection);
}
/** Selects all items in the collection. */
selectAll() {
if (this.selectionMode() === "multiple") {
this.state.setSelectedKeys(new Set(this.getAllSelectableKeys()));
}
}
/**
* Removes all keys from the selection.
*/
clearSelection() {
const selectedKeys = this.state.selectedKeys();
if (!this.disallowEmptySelection() && selectedKeys.size > 0) {
this.state.setSelectedKeys(new Selection());
}
}
/**
* Toggles between select all and an empty selection.
*/
toggleSelectAll() {
if (this.isSelectAll()) {
this.clearSelection();
} else {
this.selectAll();
}
}
select(key, e) {
if (this.selectionMode() === "none") {
return;
}
if (this.selectionMode() === "single") {
if (this.isSelected(key) && !this.disallowEmptySelection()) {
this.toggleSelection(key);
} else {
this.replaceSelection(key);
}
} else if (this.selectionBehavior() === "toggle" || e && e.pointerType === "touch") {
this.toggleSelection(key);
} else {
this.replaceSelection(key);
}
}
/** Returns whether the current selection is equal to the given selection. */
isSelectionEqual(selection) {
if (selection === this.state.selectedKeys()) {
return true;
}
const selectedKeys = this.selectedKeys();
if (selection.size !== selectedKeys.size) {
return false;
}
for (const key of selection) {
if (!selectedKeys.has(key)) {
return false;
}
}
for (const key of selectedKeys) {
if (!selection.has(key)) {
return false;
}
}
return true;
}
canSelectItem(key) {
if (this.state.selectionMode() === "none") {
return false;
}
const item = this.collection().getItem(key);
return item != null && !item.disabled;
}
isDisabled(key) {
const item = this.collection().getItem(key);
return !item || item.disabled;
}
getAllSelectableKeys() {
const keys = [];
const addKeys = (key) => {
while (key != null) {
if (this.canSelectItem(key)) {
const item = this.collection().getItem(key);
if (!item) {
continue;
}
if (item.type === "item") {
keys.push(key);
}
}
key = this.collection().getKeyAfter(key);
}
};
addKeys(this.collection().getFirstKey());
return keys;
}
};
// src/list/list-collection.ts
var ListCollection = class {
keyMap = /* @__PURE__ */ new Map();
iterable;
firstKey;
lastKey;
constructor(nodes) {
this.iterable = nodes;
for (const node of nodes) {
this.keyMap.set(node.key, node);
}
if (this.keyMap.size === 0) {
return;
}
let last;
let index = 0;
for (const [key, node] of this.keyMap) {
if (last) {
last.nextKey = key;
node.prevKey = last.key;
} else {
this.firstKey = key;
node.prevKey = void 0;
}
if (node.type === "item") {
node.index = index++;
}
last = node;
last.nextKey = void 0;
}
this.lastKey = last.key;
}
*[Symbol.iterator]() {
yield* this.iterable;
}
getSize() {
return this.keyMap.size;
}
getKeys() {
return this.keyMap.keys();
}
getKeyBefore(key) {
return this.keyMap.get(key)?.prevKey;
}
getKeyAfter(key) {
return this.keyMap.get(key)?.nextKey;
}
getFirstKey() {
return this.firstKey;
}
getLastKey() {
return this.lastKey;
}
getItem(key) {
return this.keyMap.get(key);
}
at(idx) {
const keys = [...this.getKeys()];
return this.getItem(keys[idx]);
}
};
// src/list/create-list-state.ts
import { access as access5 } from "@kobalte/utils";
import { createComputed } from "solid-js";
function createListState(props) {
const selectionState = createMultipleSelectionState(props);
const factory = (nodes) => {
return props.filter ? new ListCollection(props.filter(nodes)) : new ListCollection(nodes);
};
const collection = createCollection(
{
dataSource: () => access5(props.dataSource),
getKey: () => access5(props.getKey),
getTextValue: () => access5(props.getTextValue),
getDisabled: () => access5(props.getDisabled),
getSectionChildren: () => access5(props.getSectionChildren),
factory
},
[() => props.filter]
);
const selectionManager = new SelectionManager(collection, selectionState);
createComputed(() => {
const focusedKey = selectionState.focusedKey();
if (focusedKey != null && !collection().getItem(focusedKey)) {
selectionState.setFocusedKey(void 0);
}
});
return {
collection,
selectionManager: () => selectionManager
};
}
export {
Selection,
isSameSelection,
createMultipleSelectionState,
createTypeSelect,
createSelectableCollection,
createSelectableItem,
SelectionManager,
ListCollection,
createListState
};