364 lines
16 KiB
JavaScript
364 lines
16 KiB
JavaScript
'use client';
|
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
Object.defineProperty(exports, "useTreeItem_unstable", {
|
|
enumerable: true,
|
|
get: function() {
|
|
return useTreeItem_unstable;
|
|
}
|
|
});
|
|
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
|
|
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
|
|
const _reactdom = /*#__PURE__*/ _interop_require_wildcard._(require("react-dom"));
|
|
const _reactutilities = require("@fluentui/react-utilities");
|
|
const _keyboardkeys = require("@fluentui/keyboard-keys");
|
|
const _tokens = require("../../utils/tokens");
|
|
const _contexts = require("../../contexts");
|
|
const _getTreeItemValueFromElement = require("../../utils/getTreeItemValueFromElement");
|
|
const _reactcontextselector = require("@fluentui/react-context-selector");
|
|
const _Tree = require("../../Tree");
|
|
function useTreeItem_unstable(props, ref) {
|
|
'use no memo';
|
|
const treeType = (0, _contexts.useTreeContext_unstable)((ctx)=>ctx.treeType);
|
|
if (treeType === 'flat') {
|
|
warnIfNoProperPropsFlatTreeItem(props);
|
|
}
|
|
const requestTreeResponse = (0, _contexts.useTreeContext_unstable)((ctx)=>ctx.requestTreeResponse);
|
|
const navigationMode = (0, _contexts.useTreeContext_unstable)((ctx)=>{
|
|
var _ctx_navigationMode;
|
|
return (_ctx_navigationMode = ctx.navigationMode) !== null && _ctx_navigationMode !== void 0 ? _ctx_navigationMode : 'tree';
|
|
});
|
|
const forceUpdateRovingTabIndex = (0, _contexts.useTreeContext_unstable)((ctx)=>ctx.forceUpdateRovingTabIndex);
|
|
const { level: contextLevel } = (0, _contexts.useSubtreeContext_unstable)();
|
|
const parentValue = (0, _contexts.useTreeItemContext_unstable)((ctx)=>{
|
|
var _props_parentValue;
|
|
return (_props_parentValue = props.parentValue) !== null && _props_parentValue !== void 0 ? _props_parentValue : ctx.value;
|
|
});
|
|
// note, if the value is not externally provided,
|
|
// then selection and expansion will not work properly
|
|
const internalValue = (0, _reactutilities.useId)('fuiTreeItemValue-');
|
|
var _props_value;
|
|
const value = (_props_value = props.value) !== null && _props_value !== void 0 ? _props_value : internalValue;
|
|
const { onClick, onKeyDown, onChange, as = 'div', itemType = 'leaf', 'aria-level': level = contextLevel, 'aria-selected': ariaSelected, 'aria-expanded': ariaExpanded, ...rest } = props;
|
|
const actionsRef = _react.useRef(null);
|
|
const expandIconRef = _react.useRef(null);
|
|
const layoutRef = _react.useRef(null);
|
|
const subtreeRef = _react.useRef(null);
|
|
const selectionRef = _react.useRef(null);
|
|
const treeItemRef = _react.useRef(null);
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
// This is acceptable since the NODE_ENV will not change during runtime
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
const hasTreeContext = (0, _reactcontextselector.useHasParentContext)(_contexts.TreeContext);
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
_react.useEffect(()=>{
|
|
var _treeItemRef_current;
|
|
if (hasTreeContext) {
|
|
return;
|
|
}
|
|
if ((_treeItemRef_current = treeItemRef.current) === null || _treeItemRef_current === void 0 ? void 0 : _treeItemRef_current.querySelector(`.${_Tree.treeClassNames.root}`)) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`@fluentui/react-tree [useTreeItem]:
|
|
<TreeItem> should be declared inside a <Tree> component.`);
|
|
}
|
|
}, [
|
|
hasTreeContext
|
|
]);
|
|
}
|
|
_react.useEffect(()=>{
|
|
// When the tree item is mounted, we might need to update the roving tab index
|
|
// in edge cases where the tree is empty and then populated
|
|
forceUpdateRovingTabIndex === null || forceUpdateRovingTabIndex === void 0 ? void 0 : forceUpdateRovingTabIndex();
|
|
const treeItem = treeItemRef.current;
|
|
return ()=>{
|
|
// When the tree item is unmounted, we need to update the roving tab index
|
|
// if the tree item is the current tab indexed item
|
|
if (treeItem && treeItem.tabIndex === 0) {
|
|
forceUpdateRovingTabIndex === null || forceUpdateRovingTabIndex === void 0 ? void 0 : forceUpdateRovingTabIndex();
|
|
}
|
|
};
|
|
}, [
|
|
forceUpdateRovingTabIndex
|
|
]);
|
|
const open = (0, _contexts.useTreeContext_unstable)((ctx)=>{
|
|
var _props_open;
|
|
return (_props_open = props.open) !== null && _props_open !== void 0 ? _props_open : ctx.openItems.has(value);
|
|
});
|
|
const getNextOpen = ()=>itemType === 'branch' ? !open : open;
|
|
const selectionMode = (0, _contexts.useTreeContext_unstable)((ctx)=>ctx.selectionMode);
|
|
const checked = (0, _contexts.useTreeContext_unstable)((ctx)=>{
|
|
var _ctx_checkedItems_get;
|
|
return (_ctx_checkedItems_get = ctx.checkedItems.get(value)) !== null && _ctx_checkedItems_get !== void 0 ? _ctx_checkedItems_get : false;
|
|
});
|
|
const handleClick = (0, _reactutilities.useEventCallback)((event)=>{
|
|
var _expandIconRef_current;
|
|
const isEventFromActions = ()=>actionsRef.current && (0, _reactutilities.elementContains)(actionsRef.current, event.target);
|
|
const isEventFromSubtree = ()=>subtreeRef.current && (0, _reactutilities.elementContains)(subtreeRef.current, event.target);
|
|
const isEventFromSelection = ()=>{
|
|
var _selectionRef_current;
|
|
return (_selectionRef_current = selectionRef.current) === null || _selectionRef_current === void 0 ? void 0 : _selectionRef_current.contains(event.target);
|
|
};
|
|
const isEventFromExpandIcon = (_expandIconRef_current = expandIconRef.current) === null || _expandIconRef_current === void 0 ? void 0 : _expandIconRef_current.contains(event.target);
|
|
if (isEventFromActions() || isEventFromSubtree() || isEventFromSelection()) {
|
|
return;
|
|
} else if (!isEventFromExpandIcon) {
|
|
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
}
|
|
if (event.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
_reactdom.unstable_batchedUpdates(()=>{
|
|
const data = {
|
|
event,
|
|
value,
|
|
open: getNextOpen(),
|
|
target: event.currentTarget,
|
|
type: isEventFromExpandIcon ? _tokens.treeDataTypes.ExpandIconClick : _tokens.treeDataTypes.Click
|
|
};
|
|
if (itemType !== 'leaf') {
|
|
var _props_onOpenChange;
|
|
(_props_onOpenChange = props.onOpenChange) === null || _props_onOpenChange === void 0 ? void 0 : _props_onOpenChange.call(props, event, data);
|
|
requestTreeResponse({
|
|
...data,
|
|
itemType,
|
|
requestType: 'open'
|
|
});
|
|
}
|
|
requestTreeResponse({
|
|
...data,
|
|
itemType,
|
|
parentValue,
|
|
requestType: 'navigate',
|
|
type: _tokens.treeDataTypes.Click
|
|
});
|
|
});
|
|
});
|
|
const handleKeyDown = (0, _reactutilities.useEventCallback)((event)=>{
|
|
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
if (event.isDefaultPrevented() || !treeItemRef.current) {
|
|
return;
|
|
}
|
|
const isEventFromTreeItem = event.currentTarget === event.target;
|
|
const isEventFromActions = actionsRef.current && actionsRef.current.contains(event.target);
|
|
switch(event.key){
|
|
case _keyboardkeys.Space:
|
|
{
|
|
if (!isEventFromTreeItem) {
|
|
return;
|
|
}
|
|
if (selectionMode !== 'none') {
|
|
var _selectionRef_current;
|
|
(_selectionRef_current = selectionRef.current) === null || _selectionRef_current === void 0 ? void 0 : _selectionRef_current.click();
|
|
// Prevents the page from scrolling down when the spacebar is pressed
|
|
event.preventDefault();
|
|
}
|
|
return;
|
|
}
|
|
case _tokens.treeDataTypes.Enter:
|
|
{
|
|
if (!isEventFromTreeItem) {
|
|
return;
|
|
}
|
|
return event.currentTarget.click();
|
|
}
|
|
case _tokens.treeDataTypes.End:
|
|
case _tokens.treeDataTypes.Home:
|
|
case _tokens.treeDataTypes.ArrowUp:
|
|
{
|
|
if (!isEventFromTreeItem && !isEventFromActions) {
|
|
return;
|
|
}
|
|
return requestTreeResponse({
|
|
requestType: 'navigate',
|
|
event,
|
|
value,
|
|
itemType,
|
|
parentValue,
|
|
type: event.key,
|
|
target: event.currentTarget
|
|
});
|
|
}
|
|
case _tokens.treeDataTypes.ArrowDown:
|
|
{
|
|
if (!isEventFromTreeItem && !isEventFromActions) {
|
|
return;
|
|
}
|
|
if (isEventFromActions && (!(0, _reactutilities.isHTMLElement)(event.target) || event.target.hasAttribute('aria-haspopup'))) {
|
|
return;
|
|
}
|
|
return requestTreeResponse({
|
|
requestType: 'navigate',
|
|
event,
|
|
value,
|
|
itemType,
|
|
parentValue,
|
|
type: event.key,
|
|
target: event.currentTarget
|
|
});
|
|
}
|
|
case _tokens.treeDataTypes.ArrowLeft:
|
|
{
|
|
// arrow left with alt key is reserved for history navigation
|
|
if (event.altKey) {
|
|
return;
|
|
}
|
|
const data = {
|
|
value,
|
|
event,
|
|
open: getNextOpen(),
|
|
type: event.key,
|
|
itemType,
|
|
parentValue,
|
|
target: event.currentTarget
|
|
};
|
|
if (isEventFromActions && navigationMode === 'treegrid') {
|
|
requestTreeResponse({
|
|
...data,
|
|
requestType: 'navigate'
|
|
});
|
|
return;
|
|
}
|
|
if (!isEventFromTreeItem) {
|
|
return;
|
|
}
|
|
// do not navigate to parent if the item is on the top level
|
|
if (level === 1 && !open) {
|
|
return;
|
|
}
|
|
if (open) {
|
|
var _props_onOpenChange;
|
|
(_props_onOpenChange = props.onOpenChange) === null || _props_onOpenChange === void 0 ? void 0 : _props_onOpenChange.call(props, event, data);
|
|
}
|
|
requestTreeResponse({
|
|
...data,
|
|
requestType: open ? 'open' : 'navigate'
|
|
});
|
|
return;
|
|
}
|
|
case _tokens.treeDataTypes.ArrowRight:
|
|
{
|
|
// Ignore keyboard events that do not originate from the current tree item.
|
|
if (!isEventFromTreeItem) {
|
|
return;
|
|
}
|
|
// arrow right with alt key is reserved for history navigation
|
|
if (event.altKey) {
|
|
return;
|
|
}
|
|
const data = {
|
|
value,
|
|
event,
|
|
open: getNextOpen(),
|
|
type: event.key,
|
|
target: event.currentTarget
|
|
};
|
|
if (itemType === 'branch' && !open) {
|
|
var _props_onOpenChange1;
|
|
(_props_onOpenChange1 = props.onOpenChange) === null || _props_onOpenChange1 === void 0 ? void 0 : _props_onOpenChange1.call(props, event, data);
|
|
requestTreeResponse({
|
|
...data,
|
|
itemType,
|
|
requestType: 'open'
|
|
});
|
|
} else {
|
|
requestTreeResponse({
|
|
...data,
|
|
itemType,
|
|
parentValue,
|
|
requestType: 'navigate'
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// Ignore keyboard events that do not originate from the current tree item.
|
|
if (!isEventFromTreeItem) {
|
|
return;
|
|
}
|
|
const isTypeAheadCharacter = event.key.length === 1 && event.key.match(/\w/) && !event.altKey && !event.ctrlKey && !event.metaKey;
|
|
if (isTypeAheadCharacter) {
|
|
requestTreeResponse({
|
|
requestType: 'navigate',
|
|
event,
|
|
target: event.currentTarget,
|
|
value,
|
|
itemType,
|
|
type: _tokens.treeDataTypes.TypeAhead,
|
|
parentValue
|
|
});
|
|
}
|
|
});
|
|
const handleChange = (0, _reactutilities.useEventCallback)((event)=>{
|
|
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
if (event.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
const isEventFromSubtree = subtreeRef.current && (0, _reactutilities.elementContains)(subtreeRef.current, event.target);
|
|
if (isEventFromSubtree) {
|
|
return;
|
|
}
|
|
requestTreeResponse({
|
|
requestType: 'selection',
|
|
event,
|
|
value,
|
|
itemType,
|
|
type: 'Change',
|
|
target: event.currentTarget,
|
|
checked: checked === 'mixed' ? true : !checked
|
|
});
|
|
});
|
|
return {
|
|
value,
|
|
open,
|
|
checked,
|
|
subtreeRef,
|
|
layoutRef,
|
|
selectionRef,
|
|
expandIconRef,
|
|
treeItemRef,
|
|
actionsRef,
|
|
itemType,
|
|
level,
|
|
components: {
|
|
root: 'div'
|
|
},
|
|
// FIXME: this property is not necessary anymore, but as removing it would be a breaking change, we need to keep it as false
|
|
isAsideVisible: false,
|
|
// FIXME: this property is not necessary anymore, but as removing it would be a breaking change, we need to keep it as false
|
|
isActionsVisible: false,
|
|
root: _reactutilities.slot.always((0, _reactutilities.getIntrinsicElementProps)(as, {
|
|
tabIndex: -1,
|
|
[_getTreeItemValueFromElement.dataTreeItemValueAttrName]: value,
|
|
role: 'treeitem',
|
|
...rest,
|
|
ref: (0, _reactutilities.useMergedRefs)(ref, treeItemRef),
|
|
'aria-level': level,
|
|
'aria-checked': selectionMode === 'multiselect' ? checked : undefined,
|
|
'aria-selected': ariaSelected !== undefined ? ariaSelected : selectionMode === 'single' ? !!checked : undefined,
|
|
'aria-expanded': ariaExpanded !== undefined ? ariaExpanded : itemType === 'branch' ? open : undefined,
|
|
onClick: handleClick,
|
|
onKeyDown: handleKeyDown,
|
|
onChange: handleChange
|
|
}), {
|
|
elementType: 'div'
|
|
})
|
|
};
|
|
}
|
|
function warnIfNoProperPropsFlatTreeItem(props) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
if (props['aria-posinset'] === undefined || props['aria-setsize'] === undefined || props['aria-level'] === undefined || props.parentValue === undefined && props['aria-level'] !== 1) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`@fluentui/react-tree [${useTreeItem_unstable.name}]:
|
|
A flat treeitem must have "aria-posinset", "aria-setsize", "aria-level"
|
|
and "parentValue" (if "aria-level" > 1) to ensure a11y and navigation.
|
|
|
|
- "aria-posinset": the position of this treeitem in the current level of the tree.
|
|
- "aria-setsize": the number of siblings in this level of the tree.
|
|
- "aria-level": the current level of the treeitem.
|
|
- "parentValue": the "value" property of the parent item of this item.`);
|
|
}
|
|
}
|
|
}
|