@chakra-ui/descendant
Advanced tools
Comparing version 1.1.3 to 2.0.0
# Change Log | ||
## 2.0.0 | ||
### Major Changes | ||
- [`82f08867f`](https://github.com/chakra-ui/chakra-ui/commit/82f08867fa4825d647a3b9cc805220d9364f2f3f) | ||
[#3864](https://github.com/chakra-ui/chakra-ui/pull/3864) Thanks | ||
[@segunadebayo](https://github.com/segunadebayo)! - > Note: This is an | ||
internal package. | ||
Improve performance of registering, filtering and finding the index of | ||
descendants in a disclosure(open/close) widget. | ||
Mostly used by the accordion, menu, tabs and pin-input components. | ||
### Patch Changes | ||
- Updated dependencies | ||
[[`82f08867f`](https://github.com/chakra-ui/chakra-ui/commit/82f08867fa4825d647a3b9cc805220d9364f2f3f)]: | ||
- @chakra-ui/react-utils@1.1.2 | ||
## 1.1.3 | ||
@@ -4,0 +24,0 @@ |
@@ -12,2 +12,10 @@ "use strict"; | ||
}); | ||
var _descendant = require("./descendant"); | ||
Object.keys(_descendant).forEach(function (key) { | ||
if (key === "default" || key === "__esModule") return; | ||
if (key in exports && exports[key] === _descendant[key]) return; | ||
exports[key] = _descendant[key]; | ||
}); | ||
//# sourceMappingURL=index.js.map |
"use strict"; | ||
exports.__esModule = true; | ||
exports.useDescendants = useDescendants; | ||
exports.useDescendant = useDescendant; | ||
exports.useDescendants = useDescendants; | ||
exports.createDescendantContext = createDescendantContext; | ||
exports.useDescendantsContext = exports.DescendantsContextProvider = void 0; | ||
var _reactUtils = require("@chakra-ui/react-utils"); | ||
var _react = require("react"); | ||
var _hooks = require("@chakra-ui/hooks"); | ||
var _descendant = require("./descendant"); | ||
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } | ||
var _utils = require("./utils"); | ||
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } | ||
/** | ||
* @internal | ||
* React hook that initializes the DescendantsManager | ||
*/ | ||
function useDescendants() { | ||
var _useState = (0, _react.useState)(function () { | ||
return new _descendant.DescendantsManager(); | ||
}), | ||
descendants = _useState[0]; | ||
function useDescendant(props) { | ||
var context = props.context, | ||
element = props.element, | ||
indexProp = props.index, | ||
disabled = props.disabled, | ||
focusable = props.focusable, | ||
rest = _objectWithoutPropertiesLoose(props, ["context", "element", "index", "disabled", "focusable"]); | ||
(0, _utils.useSafeLayoutEffect)(function () { | ||
return function () { | ||
return descendants.destroy(); | ||
}; | ||
}); | ||
return descendants; | ||
} | ||
var forceUpdate = (0, _hooks.useForceUpdate)(); | ||
var register = context.register, | ||
unregister = context.unregister, | ||
descendants = context.descendants; | ||
(0, _hooks.useSafeLayoutEffect)(function () { | ||
if (!element) { | ||
forceUpdate(); | ||
} | ||
/** | ||
* Don't register this descendant if it is disabled and not focusable | ||
*/ | ||
/* ------------------------------------------------------------------------------------------------- | ||
* Descendants context to be used in component-land. | ||
- Mount the `DescendantsContextProvider` at the root of the component | ||
- Call `useDescendantsContext` anywhere you need access to the descendants information | ||
NB: I recommend using `createDescendantContext` below | ||
* -----------------------------------------------------------------------------------------------*/ | ||
var _createContext = (0, _reactUtils.createContext)({ | ||
name: "DescendantsProvider", | ||
errorMessage: "useDescendantsContext must be used within DescendantsProvider" | ||
}), | ||
DescendantsContextProvider = _createContext[0], | ||
useDescendantsContext = _createContext[1]; | ||
/** | ||
* @internal | ||
* This hook provides information a descendant such as: | ||
* - Its index compared to other descendants | ||
* - ref callback to register the descendant | ||
* - Its enabled index compared to other enabled descendants | ||
*/ | ||
if (disabled && !focusable) return undefined; | ||
/** | ||
* else, register the descendant | ||
*/ | ||
register(_extends({ | ||
element: element, | ||
disabled: disabled, | ||
focusable: focusable | ||
}, rest)); | ||
/** | ||
* when it unmounts, unregister the descendant | ||
*/ | ||
exports.useDescendantsContext = useDescendantsContext; | ||
exports.DescendantsContextProvider = DescendantsContextProvider; | ||
function useDescendant(options) { | ||
var descendants = useDescendantsContext(); | ||
var _useState2 = (0, _react.useState)(-1), | ||
index = _useState2[0], | ||
setIndex = _useState2[1]; | ||
var ref = (0, _react.useRef)(null); | ||
(0, _utils.useSafeLayoutEffect)(function () { | ||
return function () { | ||
if (element) { | ||
unregister(element); | ||
} | ||
}; // eslint-disable-next-line | ||
}, [element, disabled, focusable].concat(Object.values(rest))); | ||
var index = indexProp != null ? indexProp : descendants.findIndex(function (descendant) { | ||
return descendant.element === element; | ||
if (!ref.current) return; | ||
descendants.unregister(ref.current); | ||
}; | ||
}, []); | ||
(0, _utils.useSafeLayoutEffect)(function () { | ||
if (!ref.current) return; | ||
var dataIndex = Number(ref.current.dataset.index); | ||
if (index != dataIndex && !Number.isNaN(dataIndex)) { | ||
setIndex(dataIndex); | ||
} | ||
}); | ||
return index; | ||
var refCallback = options ? (0, _utils.cast)(descendants.register(options)) : (0, _utils.cast)(descendants.register); | ||
return { | ||
descendants: descendants, | ||
index: index, | ||
enabledIndex: descendants.enabledIndexOf(ref.current), | ||
register: (0, _reactUtils.mergeRefs)(refCallback, ref) | ||
}; | ||
} | ||
/* ------------------------------------------------------------------------------------------------- | ||
* Function that provides strongly typed versions of the context provider and hooks above. | ||
To be used in component-land | ||
* -----------------------------------------------------------------------------------------------*/ | ||
function useDescendants() { | ||
var _useState = (0, _react.useState)([]), | ||
descendants = _useState[0], | ||
setDescendants = _useState[1]; | ||
var register = (0, _react.useCallback)(function (_ref) { | ||
var element = _ref.element, | ||
rest = _objectWithoutPropertiesLoose(_ref, ["element"]); | ||
function createDescendantContext() { | ||
var ContextProvider = (0, _utils.cast)(DescendantsContextProvider); | ||
if (!element) return; // @ts-ignore | ||
var _useDescendantsContext = function _useDescendantsContext() { | ||
return (0, _utils.cast)(useDescendantsContext()); | ||
}; | ||
setDescendants(function (prevDescendants) { | ||
if (prevDescendants.find(function (item) { | ||
return item.element === element; | ||
}) == null) { | ||
var index = prevDescendants.findIndex(function (item) { | ||
if (!item.element || !element) return false; | ||
return Boolean(item.element.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING); | ||
}); | ||
var _useDescendant = function _useDescendant(options) { | ||
return useDescendant(options); | ||
}; | ||
var newItem = _extends({ | ||
element: element | ||
}, rest); | ||
var _useDescendants = function _useDescendants() { | ||
return useDescendants(); | ||
}; | ||
if (index === -1) { | ||
return [].concat(prevDescendants, [newItem]); | ||
} | ||
return [].concat(prevDescendants.slice(0, index), [newItem], prevDescendants.slice(index)); | ||
} | ||
return prevDescendants; | ||
}); | ||
}, []); | ||
var unregister = (0, _react.useCallback)(function (element) { | ||
if (!element) return; | ||
setDescendants(function (descendants) { | ||
return descendants.filter(function (descendant) { | ||
return element !== descendant.element; | ||
}); | ||
}); | ||
}, []); | ||
var context = (0, _react.useMemo)(function () { | ||
return { | ||
descendants: descendants, | ||
register: register, | ||
unregister: unregister | ||
}; | ||
}, [descendants, register, unregister]); | ||
return context; | ||
return [// context provider | ||
ContextProvider, // call this when you need to read from context | ||
_useDescendantsContext, // descendants state information, to be called and passed to `ContextProvider` | ||
_useDescendants, // descendant index information | ||
_useDescendant]; | ||
} | ||
//# sourceMappingURL=use-descendant.js.map |
export * from "./use-descendant"; | ||
export * from "./descendant"; | ||
//# sourceMappingURL=index.js.map |
@@ -1,97 +0,83 @@ | ||
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } | ||
import { createContext, mergeRefs } from "@chakra-ui/react-utils"; | ||
import { useRef, useState } from "react"; | ||
import { DescendantsManager } from "./descendant"; | ||
import { useSafeLayoutEffect, cast } from "./utils"; | ||
/** | ||
* @internal | ||
* React hook that initializes the DescendantsManager | ||
*/ | ||
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } | ||
import { useCallback, useMemo, useState } from "react"; | ||
import { useSafeLayoutEffect, useForceUpdate } from "@chakra-ui/hooks"; | ||
export function useDescendant(props) { | ||
var { | ||
context, | ||
element, | ||
index: indexProp, | ||
disabled, | ||
focusable | ||
} = props, | ||
rest = _objectWithoutPropertiesLoose(props, ["context", "element", "index", "disabled", "focusable"]); | ||
var forceUpdate = useForceUpdate(); | ||
var { | ||
register, | ||
unregister, | ||
descendants | ||
} = context; | ||
export function useDescendants() { | ||
var [descendants] = useState(() => new DescendantsManager()); | ||
useSafeLayoutEffect(() => { | ||
if (!element) { | ||
forceUpdate(); | ||
} | ||
/** | ||
* Don't register this descendant if it is disabled and not focusable | ||
*/ | ||
return () => descendants.destroy(); | ||
}); | ||
return descendants; | ||
} | ||
/* ------------------------------------------------------------------------------------------------- | ||
* Descendants context to be used in component-land. | ||
- Mount the `DescendantsContextProvider` at the root of the component | ||
- Call `useDescendantsContext` anywhere you need access to the descendants information | ||
if (disabled && !focusable) return undefined; | ||
/** | ||
* else, register the descendant | ||
*/ | ||
NB: I recommend using `createDescendantContext` below | ||
* -----------------------------------------------------------------------------------------------*/ | ||
export var [DescendantsContextProvider, useDescendantsContext] = createContext({ | ||
name: "DescendantsProvider", | ||
errorMessage: "useDescendantsContext must be used within DescendantsProvider" | ||
}); | ||
/** | ||
* @internal | ||
* This hook provides information a descendant such as: | ||
* - Its index compared to other descendants | ||
* - ref callback to register the descendant | ||
* - Its enabled index compared to other enabled descendants | ||
*/ | ||
register(_extends({ | ||
element, | ||
disabled, | ||
focusable | ||
}, rest)); | ||
/** | ||
* when it unmounts, unregister the descendant | ||
*/ | ||
export function useDescendant(options) { | ||
var descendants = useDescendantsContext(); | ||
var [index, setIndex] = useState(-1); | ||
var ref = useRef(null); | ||
useSafeLayoutEffect(() => { | ||
return () => { | ||
if (!ref.current) return; | ||
descendants.unregister(ref.current); | ||
}; | ||
}, []); | ||
useSafeLayoutEffect(() => { | ||
if (!ref.current) return; | ||
var dataIndex = Number(ref.current.dataset.index); | ||
return () => { | ||
if (element) { | ||
unregister(element); | ||
} | ||
}; // eslint-disable-next-line | ||
}, [element, disabled, focusable, ...Object.values(rest)]); | ||
var index = indexProp != null ? indexProp : descendants.findIndex(descendant => descendant.element === element); | ||
return index; | ||
if (index != dataIndex && !Number.isNaN(dataIndex)) { | ||
setIndex(dataIndex); | ||
} | ||
}); | ||
var refCallback = options ? cast(descendants.register(options)) : cast(descendants.register); | ||
return { | ||
descendants, | ||
index, | ||
enabledIndex: descendants.enabledIndexOf(ref.current), | ||
register: mergeRefs(refCallback, ref) | ||
}; | ||
} | ||
export function useDescendants() { | ||
var [descendants, setDescendants] = useState([]); | ||
var register = useCallback((_ref) => { | ||
var { | ||
element | ||
} = _ref, | ||
rest = _objectWithoutPropertiesLoose(_ref, ["element"]); | ||
/* ------------------------------------------------------------------------------------------------- | ||
* Function that provides strongly typed versions of the context provider and hooks above. | ||
To be used in component-land | ||
* -----------------------------------------------------------------------------------------------*/ | ||
if (!element) return; // @ts-ignore | ||
export function createDescendantContext() { | ||
var ContextProvider = cast(DescendantsContextProvider); | ||
setDescendants(prevDescendants => { | ||
if (prevDescendants.find(item => item.element === element) == null) { | ||
var index = prevDescendants.findIndex(item => { | ||
if (!item.element || !element) return false; | ||
return Boolean(item.element.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING); | ||
}); | ||
var _useDescendantsContext = () => cast(useDescendantsContext()); | ||
var newItem = _extends({ | ||
element | ||
}, rest); | ||
var _useDescendant = options => useDescendant(options); | ||
if (index === -1) { | ||
return [...prevDescendants, newItem]; | ||
} | ||
var _useDescendants = () => useDescendants(); | ||
return [...prevDescendants.slice(0, index), newItem, ...prevDescendants.slice(index)]; | ||
} | ||
return prevDescendants; | ||
}); | ||
}, []); | ||
var unregister = useCallback(element => { | ||
if (!element) return; | ||
setDescendants(descendants => descendants.filter(descendant => element !== descendant.element)); | ||
}, []); | ||
var context = useMemo(() => ({ | ||
descendants, | ||
register, | ||
unregister | ||
}), [descendants, register, unregister]); | ||
return context; | ||
return [// context provider | ||
ContextProvider, // call this when you need to read from context | ||
_useDescendantsContext, // descendants state information, to be called and passed to `ContextProvider` | ||
_useDescendants, // descendant index information | ||
_useDescendant]; | ||
} | ||
//# sourceMappingURL=use-descendant.js.map |
export * from "./use-descendant"; | ||
export * from "./descendant"; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,21 +0,30 @@ | ||
export declare type Descendant<T extends HTMLElement, P = {}> = P & { | ||
element: T | null; | ||
index?: number; | ||
disabled?: boolean; | ||
focusable?: boolean; | ||
}; | ||
export interface DescendantContext<T extends HTMLElement, P = {}> { | ||
descendants: Descendant<T, P>[]; | ||
register: (descendant: Descendant<T, P>) => void; | ||
unregister: (element: T) => void; | ||
/// <reference types="react" /> | ||
import { DescendantsManager, DescendantOptions } from "./descendant"; | ||
/** | ||
* @internal | ||
* React hook that initializes the DescendantsManager | ||
*/ | ||
export declare function useDescendants<T extends HTMLElement = HTMLElement, K = {}>(): DescendantsManager<T, K>; | ||
export interface UseDescendantsReturn extends ReturnType<typeof useDescendants> { | ||
} | ||
export declare type UseDescendantProps<T extends HTMLElement, P> = { | ||
context: DescendantContext<T, P>; | ||
} & Descendant<T, P>; | ||
export declare function useDescendant<T extends HTMLElement, P>(props: UseDescendantProps<T, P>): number; | ||
export declare function useDescendants<T extends HTMLElement, P>(): { | ||
descendants: Descendant<T, P>[]; | ||
register: ({ element, ...rest }: Descendant<T, P>) => void; | ||
unregister: (element: T) => void; | ||
export declare const DescendantsContextProvider: import("react").Provider<UseDescendantsReturn>, useDescendantsContext: () => UseDescendantsReturn; | ||
/** | ||
* @internal | ||
* This hook provides information a descendant such as: | ||
* - Its index compared to other descendants | ||
* - ref callback to register the descendant | ||
* - Its enabled index compared to other enabled descendants | ||
*/ | ||
export declare function useDescendant<T extends HTMLElement = HTMLElement, K = {}>(options?: DescendantOptions & K): { | ||
descendants: UseDescendantsReturn; | ||
index: number; | ||
enabledIndex: number; | ||
register: (node: T | null) => void; | ||
}; | ||
export declare function createDescendantContext<T extends HTMLElement = HTMLElement, K = {}>(): readonly [import("react").Provider<DescendantsManager<T, K>>, () => DescendantsManager<T, K>, () => DescendantsManager<T, K>, (options?: (DescendantOptions & K) | undefined) => { | ||
descendants: UseDescendantsReturn; | ||
index: number; | ||
enabledIndex: number; | ||
register: (node: T | null) => void; | ||
}]; | ||
//# sourceMappingURL=use-descendant.d.ts.map |
{ | ||
"name": "@chakra-ui/descendant", | ||
"version": "1.1.3", | ||
"version": "2.0.0", | ||
"description": "Register child nodes of a react element for better accessibility", | ||
@@ -57,3 +57,3 @@ "keywords": [ | ||
"dependencies": { | ||
"@chakra-ui/hooks": "1.5.0" | ||
"@chakra-ui/react-utils": "^1.1.2" | ||
}, | ||
@@ -60,0 +60,0 @@ "peerDependencies": { |
132
README.md
# Descendant | ||
Keep track of descendant components and their relative indices. | ||
A descendant index solution for better accessibility support in compound | ||
@@ -12,7 +14,133 @@ components. | ||
```sh | ||
yarn add @chakra-ui/descendants | ||
yarn add @chakra-ui/descendant | ||
# or | ||
npm i @chakra-ui/descendants | ||
npm i @chakra-ui/descendant | ||
``` | ||
## Motivation | ||
Descendants observer is an utility hook for keeping track of descendant elements | ||
and their relative indices. | ||
In short, this package allows each item in a list to know it's relative index | ||
and the parent of the list can keep track of each child, without needing to | ||
render in a loop and pass each component an index. | ||
This enables component composition: | ||
```jsx | ||
<List> | ||
<Item /> // I'm index 0 | ||
<Item /> // I'm index 1<div> | ||
<div> | ||
<Item /> // I'm arbitrarily nested, but still know that I'm index 2 | ||
</div> | ||
</div> | ||
</List> | ||
``` | ||
### Usage | ||
```jsx | ||
import { createDescendantContext } from "@descendants/react" | ||
import * as React from "react" | ||
const [ | ||
DescendantsProvider, | ||
useDescendantsContext, | ||
useDescendants, | ||
useDescendant, | ||
] = createDescendantContext() | ||
const MenuContext = React.createContext({}) | ||
function Menu({ children }) { | ||
// 1. Call the `useDescendants` hook | ||
const descendants = useDescendants() | ||
const [selected, setSelected] = React.useState(1) | ||
const context = React.useMemo(() => ({ selected, setSelected }), [selected]) | ||
return ( | ||
// 2. Add the descendants context | ||
<DescendantsProvider value={descendants}> | ||
<MenuContext.Provider value={context}> | ||
<div role="menu" style={{ maxWidth: 320 }}> | ||
<button | ||
onClick={() => { | ||
const prev = descendants.prev(selected) | ||
prev.node.focus() | ||
setSelected(prev.index) | ||
}} | ||
> | ||
Prev | ||
</button> | ||
<button | ||
onClick={() => { | ||
const next = descendants.next(selected) | ||
next.node.focus() | ||
setSelected(next.index) | ||
}} | ||
> | ||
Next | ||
</button> | ||
{children} | ||
</div> | ||
</MenuContext.Provider> | ||
</DescendantsProvider> | ||
) | ||
} | ||
const MenuItem = ({ children }) => { | ||
const { selected, setSelected } = React.useContext(MenuContext) | ||
// 3. Read from descendant context | ||
const { index, register } = useDescendant() | ||
const isSelected = index === selected | ||
return ( | ||
<div | ||
role="menuitem" | ||
ref={register} | ||
aria-selected={isSelected} | ||
onMouseMove={() => setSelected(index)} | ||
style={{ color: isSelected ? "red" : "black" }} | ||
> | ||
{children} - {index} | ||
</div> | ||
) | ||
} | ||
const Example = () => { | ||
const [show, setShow] = React.useState(false) | ||
const [show2, setShow2] = React.useState(false) | ||
const toggle = () => { | ||
setShow(!show) | ||
if (!show === true) { | ||
setTimeout(() => { | ||
setShow2(true) | ||
}, 1000) | ||
} | ||
} | ||
return ( | ||
<div> | ||
<button onClick={toggle}>Toggle</button> | ||
<Menu> | ||
<MenuItem>One</MenuItem> | ||
{show && <MenuItem>Two</MenuItem>} | ||
<MenuItem>Three</MenuItem> | ||
<MenuItem>Four</MenuItem> | ||
<div> | ||
{show2 && <MenuItem>Testing 🌟</MenuItem>} | ||
<MenuItem>Five</MenuItem> | ||
</div> | ||
</Menu> | ||
</div> | ||
) | ||
} | ||
``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
77621
27
617
146
1
+ Added@chakra-ui/react-utils@1.2.3(transitive)
+ Added@chakra-ui/utils@1.10.4(transitive)
- Removed@chakra-ui/hooks@1.5.0
- Removed@chakra-ui/hooks@1.5.0(transitive)
- Removed@chakra-ui/react-utils@1.1.1(transitive)
- Removed@chakra-ui/utils@1.6.0(transitive)
- Removedcompute-scroll-into-view@1.0.14(transitive)
- Removedcopy-to-clipboard@3.3.1(transitive)
- Removedtoggle-selection@1.0.6(transitive)