@bento/focus-lock 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/index.cjs +128 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -53
- package/dist/index.d.cts.map +1 -0
- package/dist/{index.d.ts → index.d.mts} +55 -53
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +107 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +23 -18
- package/src/index.tsx +2 -2
- package/dist/index.js +0 -38
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -18,19 +18,19 @@ The following properties are available to be used on the `FocusLock` component:
|
|
|
18
18
|
|
|
19
19
|
| Prop | Type | Required | Description |
|
|
20
20
|
|------|------|----------|------------|
|
|
21
|
-
| `contain` | `boolean` | No | Whether to contain focus within the scope.
|
|
21
|
+
| `contain` | `boolean \| undefined` | No | Whether to contain focus within the scope.
|
|
22
22
|
When true, focus will cycle between focusable elements within the scope. |
|
|
23
|
-
| `restoreFocus` | `boolean` | No | Whether to restore focus to the previously focused element when the focus scope unmounts. |
|
|
24
|
-
| `autoFocus` | `boolean` | No | Whether to automatically focus the first focusable element when the focus scope mounts. |
|
|
23
|
+
| `restoreFocus` | `boolean \| undefined` | No | Whether to restore focus to the previously focused element when the focus scope unmounts. |
|
|
24
|
+
| `autoFocus` | `boolean \| undefined` | No | Whether to automatically focus the first focusable element when the focus scope mounts. |
|
|
25
25
|
| `children` | `ReactNode` | No | The content to render inside the focus lock.
|
|
26
26
|
Can be a single element or multiple elements. |
|
|
27
|
-
| `onFocusEnter` | `(e: FocusEvent<Element, Element>) => void` | No | Callback fired when focus enters the scope |
|
|
28
|
-
| `onFocusLeave` | `(e: FocusEvent<Element, Element>) => void` | No | Callback fired when focus leaves the scope |
|
|
29
|
-
| `className` | `string \| ((state: FocusLockState) => string)` | No | Render prop for className |
|
|
30
|
-
| `style` | `((state: FocusLockState) => CSSProperties) \| CSSProperties` | No | Render prop for style |
|
|
31
|
-
| `slot` | `string` | No | A named part of a component that can be customized. This is implemented by the consuming component.
|
|
27
|
+
| `onFocusEnter` | `((e: FocusEvent<Element, Element>) => void) \| undefined` | No | Callback fired when focus enters the scope |
|
|
28
|
+
| `onFocusLeave` | `((e: FocusEvent<Element, Element>) => void) \| undefined` | No | Callback fired when focus leaves the scope |
|
|
29
|
+
| `className` | `string \| ((state: FocusLockState) => string) \| undefined` | No | Render prop for className |
|
|
30
|
+
| `style` | `((state: FocusLockState) => CSSProperties) \| CSSProperties \| undefined` | No | Render prop for style |
|
|
31
|
+
| `slot` | `string \| undefined` | No | A named part of a component that can be customized. This is implemented by the consuming component.
|
|
32
32
|
The exposed slot names of a component are available in the components documentation. |
|
|
33
|
-
| `slots` | `Record<string, object \| Function
|
|
33
|
+
| `slots` | `Record<string, object \| Function> \| undefined` | No | An object that contains the customizations for the slots.
|
|
34
34
|
The main way you interact with the slot system as a consumer. |
|
|
35
35
|
|
|
36
36
|
For all other properties specified on the `FocusLock` component, they will be
|
package/dist/index.cjs
CHANGED
|
@@ -1,44 +1,131 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let react = require("react");
|
|
25
|
+
react = __toESM(react);
|
|
26
|
+
let _bento_use_data_attributes = require("@bento/use-data-attributes");
|
|
27
|
+
let _react_aria_interactions = require("@react-aria/interactions");
|
|
28
|
+
let _bento_slots = require("@bento/slots");
|
|
29
|
+
let _react_aria_focus = require("@react-aria/focus");
|
|
30
|
+
let _bento_use_props = require("@bento/use-props");
|
|
31
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
32
|
+
//#region src/index.tsx
|
|
33
|
+
/**
|
|
34
|
+
* FocusLock manages focus within a scope, preventing focus from escaping and optionally
|
|
35
|
+
* restoring focus when the scope is removed. Built on top of React ARIA's FocusScope.
|
|
36
|
+
*
|
|
37
|
+
* The FocusLock primitive provides essential focus management for modals, dialogs, drawers,
|
|
38
|
+
* select popovers, and other overlay components that need to trap focus within their boundaries.
|
|
39
|
+
*
|
|
40
|
+
* This component does not add any wrapper elements - it applies data attributes directly to
|
|
41
|
+
* its children, allowing for flexible composition with multiple elements or single elements.
|
|
42
|
+
*
|
|
43
|
+
* @component
|
|
44
|
+
* @param args - The properties {@link FocusLockProps} passed to the FocusLock component.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* // Overlay with multiple children (backdrop + content)
|
|
49
|
+
* <FocusLock contain restoreFocus autoFocus>
|
|
50
|
+
* <div slot="backdrop" />
|
|
51
|
+
* <div slot="content">
|
|
52
|
+
* <h2>Modal Title</h2>
|
|
53
|
+
* <button>Close</button>
|
|
54
|
+
* </div>
|
|
55
|
+
* </FocusLock>
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* // Select popover with single child
|
|
61
|
+
* <FocusLock contain restoreFocus autoFocus>
|
|
62
|
+
* <div className="popover">
|
|
63
|
+
* <ListBox>
|
|
64
|
+
* <ListBoxItem>Option 1</ListBoxItem>
|
|
65
|
+
* <ListBoxItem>Option 2</ListBoxItem>
|
|
66
|
+
* </ListBox>
|
|
67
|
+
* </div>
|
|
68
|
+
* </FocusLock>
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* // With render props for dynamic styling
|
|
74
|
+
* <FocusLock
|
|
75
|
+
* contain
|
|
76
|
+
* className={({ hasFocus, isContained }) =>
|
|
77
|
+
* `modal ${isContained ? 'contained' : ''} ${hasFocus ? 'focused' : ''}`
|
|
78
|
+
* }
|
|
79
|
+
* style={({ hasFocus }) => ({
|
|
80
|
+
* opacity: hasFocus ? 1 : 0.8
|
|
81
|
+
* })}
|
|
82
|
+
* >
|
|
83
|
+
* <div>Content</div>
|
|
84
|
+
* </FocusLock>
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* @public
|
|
88
|
+
*/
|
|
89
|
+
const FocusLock = (0, _bento_slots.withSlots)("BentoFocusLock", function FocusLock(args) {
|
|
90
|
+
const { contain = false, restoreFocus = false, autoFocus = false, children, onFocusEnter, onFocusLeave } = args;
|
|
91
|
+
const [hasFocus, setHasFocus] = (0, react.useState)(false);
|
|
92
|
+
const { focusWithinProps } = (0, _react_aria_interactions.useFocusWithin)({
|
|
93
|
+
onFocusWithinChange: function onFocusWithinChange(isFocusWithin) {
|
|
94
|
+
setHasFocus(isFocusWithin);
|
|
95
|
+
},
|
|
96
|
+
onFocusWithin: onFocusEnter,
|
|
97
|
+
onBlurWithin: onFocusLeave
|
|
98
|
+
});
|
|
99
|
+
const { apply } = (0, _bento_use_props.useProps)(args, {
|
|
100
|
+
hasFocus,
|
|
101
|
+
isContained: contain
|
|
102
|
+
});
|
|
103
|
+
const data = (0, _bento_use_data_attributes.useDataAttributes)({
|
|
104
|
+
"focus-contained": contain,
|
|
105
|
+
"has-focus": hasFocus
|
|
106
|
+
});
|
|
107
|
+
if (!children) return null;
|
|
108
|
+
const spread = apply({
|
|
109
|
+
contain,
|
|
110
|
+
restoreFocus,
|
|
111
|
+
autoFocus
|
|
112
|
+
}, [
|
|
113
|
+
"children",
|
|
114
|
+
"onFocusEnter",
|
|
115
|
+
"onFocusLeave"
|
|
116
|
+
]);
|
|
117
|
+
const kids = {
|
|
118
|
+
...focusWithinProps,
|
|
119
|
+
...data
|
|
120
|
+
};
|
|
121
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_aria_focus.FocusScope, {
|
|
122
|
+
...spread,
|
|
123
|
+
children: react.default.Children.map(children, function applyDataAttributes(child) {
|
|
124
|
+
return (0, react.isValidElement)(child) ? (0, react.cloneElement)(child, kids) : child;
|
|
125
|
+
})
|
|
126
|
+
});
|
|
40
127
|
});
|
|
41
|
-
|
|
128
|
+
//#endregion
|
|
42
129
|
exports.FocusLock = FocusLock;
|
|
43
|
-
|
|
130
|
+
|
|
44
131
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["FocusScope","React"],"sources":["../src/index.tsx"],"sourcesContent":["import React, { cloneElement, isValidElement, type ReactNode, useState } from 'react';\nimport { useDataAttributes } from '@bento/use-data-attributes';\nimport { useFocusWithin } from '@react-aria/interactions';\nimport { withSlots, type Slots } from '@bento/slots';\nimport { FocusScope } from '@react-aria/focus';\nimport { useProps } from '@bento/use-props';\n\n/**\n * State object passed to render prop functions\n * @public\n */\nexport interface FocusLockState {\n /**\n * Whether focus is currently within the scope\n */\n hasFocus: boolean;\n\n /**\n * Whether focus is contained within the scope\n */\n isContained: boolean;\n}\n\nexport interface FocusLockProps extends Slots {\n /**\n * Whether to contain focus within the scope.\n * When true, focus will cycle between focusable elements within the scope.\n *\n * @default false\n */\n contain?: boolean;\n\n /**\n * Whether to restore focus to the previously focused element when the focus scope unmounts.\n *\n * @default false\n */\n restoreFocus?: boolean;\n\n /**\n * Whether to automatically focus the first focusable element when the focus scope mounts.\n *\n * @default false\n */\n autoFocus?: boolean;\n\n /**\n * The content to render inside the focus lock.\n * Can be a single element or multiple elements.\n */\n children?: ReactNode;\n\n /**\n * Callback fired when focus enters the scope\n */\n onFocusEnter?: (e: React.FocusEvent) => void;\n\n /**\n * Callback fired when focus leaves the scope\n */\n onFocusLeave?: (e: React.FocusEvent) => void;\n\n /**\n * Render prop for className\n */\n className?: ((state: FocusLockState) => string) | string;\n\n /**\n * Render prop for style\n */\n style?: ((state: FocusLockState) => React.CSSProperties) | React.CSSProperties;\n}\n\n/**\n * FocusLock manages focus within a scope, preventing focus from escaping and optionally\n * restoring focus when the scope is removed. Built on top of React ARIA's FocusScope.\n *\n * The FocusLock primitive provides essential focus management for modals, dialogs, drawers,\n * select popovers, and other overlay components that need to trap focus within their boundaries.\n *\n * This component does not add any wrapper elements - it applies data attributes directly to\n * its children, allowing for flexible composition with multiple elements or single elements.\n *\n * @component\n * @param args - The properties {@link FocusLockProps} passed to the FocusLock component.\n *\n * @example\n * ```tsx\n * // Overlay with multiple children (backdrop + content)\n * <FocusLock contain restoreFocus autoFocus>\n * <div slot=\"backdrop\" />\n * <div slot=\"content\">\n * <h2>Modal Title</h2>\n * <button>Close</button>\n * </div>\n * </FocusLock>\n * ```\n *\n * @example\n * ```tsx\n * // Select popover with single child\n * <FocusLock contain restoreFocus autoFocus>\n * <div className=\"popover\">\n * <ListBox>\n * <ListBoxItem>Option 1</ListBoxItem>\n * <ListBoxItem>Option 2</ListBoxItem>\n * </ListBox>\n * </div>\n * </FocusLock>\n * ```\n *\n * @example\n * ```tsx\n * // With render props for dynamic styling\n * <FocusLock\n * contain\n * className={({ hasFocus, isContained }) =>\n * `modal ${isContained ? 'contained' : ''} ${hasFocus ? 'focused' : ''}`\n * }\n * style={({ hasFocus }) => ({\n * opacity: hasFocus ? 1 : 0.8\n * })}\n * >\n * <div>Content</div>\n * </FocusLock>\n * ```\n *\n * @public\n */\nexport const FocusLock = withSlots('BentoFocusLock', function FocusLock(args: FocusLockProps) {\n const { contain = false, restoreFocus = false, autoFocus = false, children, onFocusEnter, onFocusLeave } = args;\n const [hasFocus, setHasFocus] = useState(false);\n\n // Track focus within the scope using React ARIA\n const { focusWithinProps } = useFocusWithin({\n onFocusWithinChange: function onFocusWithinChange(isFocusWithin) {\n setHasFocus(isFocusWithin);\n },\n onFocusWithin: onFocusEnter,\n onBlurWithin: onFocusLeave\n });\n\n // Create state object for render props\n const state: FocusLockState = {\n hasFocus,\n isContained: contain\n };\n\n // Pass state to useProps so render props can access it\n const { apply } = useProps(args, state);\n\n // Generate data attributes for focus lock state\n const data = useDataAttributes({\n 'focus-contained': contain,\n 'has-focus': hasFocus\n });\n\n if (!children) return null;\n\n //\n // Apply props to FocusScope for slot inheritance\n //\n const spread = apply({ contain, restoreFocus, autoFocus }, ['children', 'onFocusEnter', 'onFocusLeave']);\n\n //\n // Merge focus tracking props with data attributes to apply to children\n //\n const kids = { ...focusWithinProps, ...data };\n\n return (\n <FocusScope {...spread}>\n {React.Children.map(children, function applyDataAttributes(child) {\n return isValidElement(child) ? cloneElement(child as React.ReactElement, kids) : child;\n })}\n </FocusScope>\n );\n}) as (props: FocusLockProps) => React.ReactElement | null;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiIA,MAAa,aAAA,GAAA,aAAA,WAAsB,kBAAkB,SAAS,UAAU,MAAsB;CAC5F,MAAM,EAAE,UAAU,OAAO,eAAe,OAAO,YAAY,OAAO,UAAU,cAAc,iBAAiB;CAC3G,MAAM,CAAC,UAAU,gBAAA,GAAA,MAAA,UAAwB,KAAK;CAG9C,MAAM,EAAE,sBAAA,GAAA,yBAAA,gBAAoC;EAC1C,qBAAqB,SAAS,oBAAoB,eAAe;GAC/D,YAAY,aAAa;EAC3B;EACA,eAAe;EACf,cAAc;CAChB,CAAC;CASD,MAAM,EAAE,WAAA,GAAA,iBAAA,UAAmB,MAAM;EAL/B;EACA,aAAa;CAIsB,CAAC;CAGtC,MAAM,QAAA,GAAA,2BAAA,mBAAyB;EAC7B,mBAAmB;EACnB,aAAa;CACf,CAAC;CAED,IAAI,CAAC,UAAU,OAAO;CAKtB,MAAM,SAAS,MAAM;EAAE;EAAS;EAAc;CAAU,GAAG;EAAC;EAAY;EAAgB;CAAc,CAAC;CAKvG,MAAM,OAAO;EAAE,GAAG;EAAkB,GAAG;CAAK;CAE5C,OACE,iBAAA,GAAA,kBAAA,KAACA,kBAAAA,YAAD;EAAY,GAAI;YACbC,MAAAA,QAAM,SAAS,IAAI,UAAU,SAAS,oBAAoB,OAAO;GAChE,QAAA,GAAA,MAAA,gBAAsB,KAAK,KAAA,GAAA,MAAA,cAAiB,OAA6B,IAAI,IAAI;EACnF,CAAC;CACS,CAAA;AAEhB,CAAC"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,61 +1,62 @@
|
|
|
1
|
-
import React, { ReactNode } from
|
|
2
|
-
import { Slots } from
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import { Slots } from "@bento/slots";
|
|
3
3
|
|
|
4
|
+
//#region src/index.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* State object passed to render prop functions
|
|
6
7
|
* @public
|
|
7
8
|
*/
|
|
8
9
|
interface FocusLockState {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Whether focus is currently within the scope
|
|
12
|
+
*/
|
|
13
|
+
hasFocus: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Whether focus is contained within the scope
|
|
16
|
+
*/
|
|
17
|
+
isContained: boolean;
|
|
17
18
|
}
|
|
18
19
|
interface FocusLockProps extends Slots {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Whether to contain focus within the scope.
|
|
22
|
+
* When true, focus will cycle between focusable elements within the scope.
|
|
23
|
+
*
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
contain?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to restore focus to the previously focused element when the focus scope unmounts.
|
|
29
|
+
*
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
restoreFocus?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether to automatically focus the first focusable element when the focus scope mounts.
|
|
35
|
+
*
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
autoFocus?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The content to render inside the focus lock.
|
|
41
|
+
* Can be a single element or multiple elements.
|
|
42
|
+
*/
|
|
43
|
+
children?: ReactNode;
|
|
44
|
+
/**
|
|
45
|
+
* Callback fired when focus enters the scope
|
|
46
|
+
*/
|
|
47
|
+
onFocusEnter?: (e: React.FocusEvent) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Callback fired when focus leaves the scope
|
|
50
|
+
*/
|
|
51
|
+
onFocusLeave?: (e: React.FocusEvent) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Render prop for className
|
|
54
|
+
*/
|
|
55
|
+
className?: ((state: FocusLockState) => string) | string;
|
|
56
|
+
/**
|
|
57
|
+
* Render prop for style
|
|
58
|
+
*/
|
|
59
|
+
style?: ((state: FocusLockState) => React.CSSProperties) | React.CSSProperties;
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
61
62
|
* FocusLock manages focus within a scope, preventing focus from escaping and optionally
|
|
@@ -113,6 +114,7 @@ interface FocusLockProps extends Slots {
|
|
|
113
114
|
*
|
|
114
115
|
* @public
|
|
115
116
|
*/
|
|
116
|
-
declare const FocusLock: (props: FocusLockProps) => React.ReactElement | null;
|
|
117
|
-
|
|
118
|
-
export { FocusLock,
|
|
117
|
+
declare const FocusLock: (props: FocusLockProps) => React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | null;
|
|
118
|
+
//#endregion
|
|
119
|
+
export { FocusLock, FocusLockProps, FocusLockState };
|
|
120
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.tsx"],"mappings":";;;;;;AAWA;;UAAiB,cAAA;EAIf;AAKW;AAGb;EARE,QAAA;;;;EAKA,WAAW;AAAA;AAAA,UAGI,cAAA,SAAuB,KAAA;EA+CF;;;;;;EAxCpC,OAAA;EAOA;;;;;EAAA,YAAA;EAkByB;;;;;EAXzB,SAAA;EAqBA;;;;EAfA,QAAA,GAAW,SAAA;EAoBD;;;EAfV,YAAA,IAAgB,CAAA,EAAG,KAAA,CAAM,UAAA;EAewC;;AAAa;EAV9E,YAAA,IAAgB,CAAA,EAAG,KAAA,CAAM,UAAA;EAoH+B;;;EA/GxD,SAAA,KAAc,KAAA,EAAO,cAAA;EA+GU;;;EA1G/B,KAAA,KAAU,KAAA,EAAO,cAAA,KAAmB,KAAA,CAAM,aAAA,IAAiB,KAAA,CAAM,aAAA;AAAA;;;;;;AA0GlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA/CpB,SAAA,GA+CN,KAAA,EAAK,cAAA,KAAqB,KAAA,CAAA,YAAA,mBAAA,KAAA,CAAA,qBAAA"}
|
|
@@ -1,61 +1,62 @@
|
|
|
1
|
-
import React, { ReactNode } from
|
|
2
|
-
import { Slots } from
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import { Slots } from "@bento/slots";
|
|
3
3
|
|
|
4
|
+
//#region src/index.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* State object passed to render prop functions
|
|
6
7
|
* @public
|
|
7
8
|
*/
|
|
8
9
|
interface FocusLockState {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Whether focus is currently within the scope
|
|
12
|
+
*/
|
|
13
|
+
hasFocus: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Whether focus is contained within the scope
|
|
16
|
+
*/
|
|
17
|
+
isContained: boolean;
|
|
17
18
|
}
|
|
18
19
|
interface FocusLockProps extends Slots {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Whether to contain focus within the scope.
|
|
22
|
+
* When true, focus will cycle between focusable elements within the scope.
|
|
23
|
+
*
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
contain?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to restore focus to the previously focused element when the focus scope unmounts.
|
|
29
|
+
*
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
restoreFocus?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether to automatically focus the first focusable element when the focus scope mounts.
|
|
35
|
+
*
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
autoFocus?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The content to render inside the focus lock.
|
|
41
|
+
* Can be a single element or multiple elements.
|
|
42
|
+
*/
|
|
43
|
+
children?: ReactNode;
|
|
44
|
+
/**
|
|
45
|
+
* Callback fired when focus enters the scope
|
|
46
|
+
*/
|
|
47
|
+
onFocusEnter?: (e: React.FocusEvent) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Callback fired when focus leaves the scope
|
|
50
|
+
*/
|
|
51
|
+
onFocusLeave?: (e: React.FocusEvent) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Render prop for className
|
|
54
|
+
*/
|
|
55
|
+
className?: ((state: FocusLockState) => string) | string;
|
|
56
|
+
/**
|
|
57
|
+
* Render prop for style
|
|
58
|
+
*/
|
|
59
|
+
style?: ((state: FocusLockState) => React.CSSProperties) | React.CSSProperties;
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
61
62
|
* FocusLock manages focus within a scope, preventing focus from escaping and optionally
|
|
@@ -113,6 +114,7 @@ interface FocusLockProps extends Slots {
|
|
|
113
114
|
*
|
|
114
115
|
* @public
|
|
115
116
|
*/
|
|
116
|
-
declare const FocusLock: (props: FocusLockProps) => React.ReactElement | null;
|
|
117
|
-
|
|
118
|
-
export { FocusLock,
|
|
117
|
+
declare const FocusLock: (props: FocusLockProps) => React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | null;
|
|
118
|
+
//#endregion
|
|
119
|
+
export { FocusLock, FocusLockProps, FocusLockState };
|
|
120
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.tsx"],"mappings":";;;;;;AAWA;;UAAiB,cAAA;EAIf;AAKW;AAGb;EARE,QAAA;;;;EAKA,WAAW;AAAA;AAAA,UAGI,cAAA,SAAuB,KAAA;EA+CF;;;;;;EAxCpC,OAAA;EAOA;;;;;EAAA,YAAA;EAkByB;;;;;EAXzB,SAAA;EAqBA;;;;EAfA,QAAA,GAAW,SAAA;EAoBD;;;EAfV,YAAA,IAAgB,CAAA,EAAG,KAAA,CAAM,UAAA;EAewC;;AAAa;EAV9E,YAAA,IAAgB,CAAA,EAAG,KAAA,CAAM,UAAA;EAoH+B;;;EA/GxD,SAAA,KAAc,KAAA,EAAO,cAAA;EA+GU;;;EA1G/B,KAAA,KAAU,KAAA,EAAO,cAAA,KAAmB,KAAA,CAAM,aAAA,IAAiB,KAAA,CAAM,aAAA;AAAA;;;;;;AA0GlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA/CpB,SAAA,GA+CN,KAAA,EAAK,cAAA,KAAqB,KAAA,CAAA,YAAA,mBAAA,KAAA,CAAA,qBAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, { cloneElement, isValidElement, useState } from "react";
|
|
2
|
+
import { useDataAttributes } from "@bento/use-data-attributes";
|
|
3
|
+
import { useFocusWithin } from "@react-aria/interactions";
|
|
4
|
+
import { withSlots } from "@bento/slots";
|
|
5
|
+
import { FocusScope } from "@react-aria/focus";
|
|
6
|
+
import { useProps } from "@bento/use-props";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/index.tsx
|
|
9
|
+
/**
|
|
10
|
+
* FocusLock manages focus within a scope, preventing focus from escaping and optionally
|
|
11
|
+
* restoring focus when the scope is removed. Built on top of React ARIA's FocusScope.
|
|
12
|
+
*
|
|
13
|
+
* The FocusLock primitive provides essential focus management for modals, dialogs, drawers,
|
|
14
|
+
* select popovers, and other overlay components that need to trap focus within their boundaries.
|
|
15
|
+
*
|
|
16
|
+
* This component does not add any wrapper elements - it applies data attributes directly to
|
|
17
|
+
* its children, allowing for flexible composition with multiple elements or single elements.
|
|
18
|
+
*
|
|
19
|
+
* @component
|
|
20
|
+
* @param args - The properties {@link FocusLockProps} passed to the FocusLock component.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* // Overlay with multiple children (backdrop + content)
|
|
25
|
+
* <FocusLock contain restoreFocus autoFocus>
|
|
26
|
+
* <div slot="backdrop" />
|
|
27
|
+
* <div slot="content">
|
|
28
|
+
* <h2>Modal Title</h2>
|
|
29
|
+
* <button>Close</button>
|
|
30
|
+
* </div>
|
|
31
|
+
* </FocusLock>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // Select popover with single child
|
|
37
|
+
* <FocusLock contain restoreFocus autoFocus>
|
|
38
|
+
* <div className="popover">
|
|
39
|
+
* <ListBox>
|
|
40
|
+
* <ListBoxItem>Option 1</ListBoxItem>
|
|
41
|
+
* <ListBoxItem>Option 2</ListBoxItem>
|
|
42
|
+
* </ListBox>
|
|
43
|
+
* </div>
|
|
44
|
+
* </FocusLock>
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* // With render props for dynamic styling
|
|
50
|
+
* <FocusLock
|
|
51
|
+
* contain
|
|
52
|
+
* className={({ hasFocus, isContained }) =>
|
|
53
|
+
* `modal ${isContained ? 'contained' : ''} ${hasFocus ? 'focused' : ''}`
|
|
54
|
+
* }
|
|
55
|
+
* style={({ hasFocus }) => ({
|
|
56
|
+
* opacity: hasFocus ? 1 : 0.8
|
|
57
|
+
* })}
|
|
58
|
+
* >
|
|
59
|
+
* <div>Content</div>
|
|
60
|
+
* </FocusLock>
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
const FocusLock = withSlots("BentoFocusLock", function FocusLock(args) {
|
|
66
|
+
const { contain = false, restoreFocus = false, autoFocus = false, children, onFocusEnter, onFocusLeave } = args;
|
|
67
|
+
const [hasFocus, setHasFocus] = useState(false);
|
|
68
|
+
const { focusWithinProps } = useFocusWithin({
|
|
69
|
+
onFocusWithinChange: function onFocusWithinChange(isFocusWithin) {
|
|
70
|
+
setHasFocus(isFocusWithin);
|
|
71
|
+
},
|
|
72
|
+
onFocusWithin: onFocusEnter,
|
|
73
|
+
onBlurWithin: onFocusLeave
|
|
74
|
+
});
|
|
75
|
+
const { apply } = useProps(args, {
|
|
76
|
+
hasFocus,
|
|
77
|
+
isContained: contain
|
|
78
|
+
});
|
|
79
|
+
const data = useDataAttributes({
|
|
80
|
+
"focus-contained": contain,
|
|
81
|
+
"has-focus": hasFocus
|
|
82
|
+
});
|
|
83
|
+
if (!children) return null;
|
|
84
|
+
const spread = apply({
|
|
85
|
+
contain,
|
|
86
|
+
restoreFocus,
|
|
87
|
+
autoFocus
|
|
88
|
+
}, [
|
|
89
|
+
"children",
|
|
90
|
+
"onFocusEnter",
|
|
91
|
+
"onFocusLeave"
|
|
92
|
+
]);
|
|
93
|
+
const kids = {
|
|
94
|
+
...focusWithinProps,
|
|
95
|
+
...data
|
|
96
|
+
};
|
|
97
|
+
return /* @__PURE__ */ jsx(FocusScope, {
|
|
98
|
+
...spread,
|
|
99
|
+
children: React.Children.map(children, function applyDataAttributes(child) {
|
|
100
|
+
return isValidElement(child) ? cloneElement(child, kids) : child;
|
|
101
|
+
})
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
//#endregion
|
|
105
|
+
export { FocusLock };
|
|
106
|
+
|
|
107
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.tsx"],"sourcesContent":["import React, { cloneElement, isValidElement, type ReactNode, useState } from 'react';\nimport { useDataAttributes } from '@bento/use-data-attributes';\nimport { useFocusWithin } from '@react-aria/interactions';\nimport { withSlots, type Slots } from '@bento/slots';\nimport { FocusScope } from '@react-aria/focus';\nimport { useProps } from '@bento/use-props';\n\n/**\n * State object passed to render prop functions\n * @public\n */\nexport interface FocusLockState {\n /**\n * Whether focus is currently within the scope\n */\n hasFocus: boolean;\n\n /**\n * Whether focus is contained within the scope\n */\n isContained: boolean;\n}\n\nexport interface FocusLockProps extends Slots {\n /**\n * Whether to contain focus within the scope.\n * When true, focus will cycle between focusable elements within the scope.\n *\n * @default false\n */\n contain?: boolean;\n\n /**\n * Whether to restore focus to the previously focused element when the focus scope unmounts.\n *\n * @default false\n */\n restoreFocus?: boolean;\n\n /**\n * Whether to automatically focus the first focusable element when the focus scope mounts.\n *\n * @default false\n */\n autoFocus?: boolean;\n\n /**\n * The content to render inside the focus lock.\n * Can be a single element or multiple elements.\n */\n children?: ReactNode;\n\n /**\n * Callback fired when focus enters the scope\n */\n onFocusEnter?: (e: React.FocusEvent) => void;\n\n /**\n * Callback fired when focus leaves the scope\n */\n onFocusLeave?: (e: React.FocusEvent) => void;\n\n /**\n * Render prop for className\n */\n className?: ((state: FocusLockState) => string) | string;\n\n /**\n * Render prop for style\n */\n style?: ((state: FocusLockState) => React.CSSProperties) | React.CSSProperties;\n}\n\n/**\n * FocusLock manages focus within a scope, preventing focus from escaping and optionally\n * restoring focus when the scope is removed. Built on top of React ARIA's FocusScope.\n *\n * The FocusLock primitive provides essential focus management for modals, dialogs, drawers,\n * select popovers, and other overlay components that need to trap focus within their boundaries.\n *\n * This component does not add any wrapper elements - it applies data attributes directly to\n * its children, allowing for flexible composition with multiple elements or single elements.\n *\n * @component\n * @param args - The properties {@link FocusLockProps} passed to the FocusLock component.\n *\n * @example\n * ```tsx\n * // Overlay with multiple children (backdrop + content)\n * <FocusLock contain restoreFocus autoFocus>\n * <div slot=\"backdrop\" />\n * <div slot=\"content\">\n * <h2>Modal Title</h2>\n * <button>Close</button>\n * </div>\n * </FocusLock>\n * ```\n *\n * @example\n * ```tsx\n * // Select popover with single child\n * <FocusLock contain restoreFocus autoFocus>\n * <div className=\"popover\">\n * <ListBox>\n * <ListBoxItem>Option 1</ListBoxItem>\n * <ListBoxItem>Option 2</ListBoxItem>\n * </ListBox>\n * </div>\n * </FocusLock>\n * ```\n *\n * @example\n * ```tsx\n * // With render props for dynamic styling\n * <FocusLock\n * contain\n * className={({ hasFocus, isContained }) =>\n * `modal ${isContained ? 'contained' : ''} ${hasFocus ? 'focused' : ''}`\n * }\n * style={({ hasFocus }) => ({\n * opacity: hasFocus ? 1 : 0.8\n * })}\n * >\n * <div>Content</div>\n * </FocusLock>\n * ```\n *\n * @public\n */\nexport const FocusLock = withSlots('BentoFocusLock', function FocusLock(args: FocusLockProps) {\n const { contain = false, restoreFocus = false, autoFocus = false, children, onFocusEnter, onFocusLeave } = args;\n const [hasFocus, setHasFocus] = useState(false);\n\n // Track focus within the scope using React ARIA\n const { focusWithinProps } = useFocusWithin({\n onFocusWithinChange: function onFocusWithinChange(isFocusWithin) {\n setHasFocus(isFocusWithin);\n },\n onFocusWithin: onFocusEnter,\n onBlurWithin: onFocusLeave\n });\n\n // Create state object for render props\n const state: FocusLockState = {\n hasFocus,\n isContained: contain\n };\n\n // Pass state to useProps so render props can access it\n const { apply } = useProps(args, state);\n\n // Generate data attributes for focus lock state\n const data = useDataAttributes({\n 'focus-contained': contain,\n 'has-focus': hasFocus\n });\n\n if (!children) return null;\n\n //\n // Apply props to FocusScope for slot inheritance\n //\n const spread = apply({ contain, restoreFocus, autoFocus }, ['children', 'onFocusEnter', 'onFocusLeave']);\n\n //\n // Merge focus tracking props with data attributes to apply to children\n //\n const kids = { ...focusWithinProps, ...data };\n\n return (\n <FocusScope {...spread}>\n {React.Children.map(children, function applyDataAttributes(child) {\n return isValidElement(child) ? cloneElement(child as React.ReactElement, kids) : child;\n })}\n </FocusScope>\n );\n}) as (props: FocusLockProps) => React.ReactElement | null;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiIA,MAAa,YAAY,UAAU,kBAAkB,SAAS,UAAU,MAAsB;CAC5F,MAAM,EAAE,UAAU,OAAO,eAAe,OAAO,YAAY,OAAO,UAAU,cAAc,iBAAiB;CAC3G,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAG9C,MAAM,EAAE,qBAAqB,eAAe;EAC1C,qBAAqB,SAAS,oBAAoB,eAAe;GAC/D,YAAY,aAAa;EAC3B;EACA,eAAe;EACf,cAAc;CAChB,CAAC;CASD,MAAM,EAAE,UAAU,SAAS,MAAM;EAL/B;EACA,aAAa;CAIsB,CAAC;CAGtC,MAAM,OAAO,kBAAkB;EAC7B,mBAAmB;EACnB,aAAa;CACf,CAAC;CAED,IAAI,CAAC,UAAU,OAAO;CAKtB,MAAM,SAAS,MAAM;EAAE;EAAS;EAAc;CAAU,GAAG;EAAC;EAAY;EAAgB;CAAc,CAAC;CAKvG,MAAM,OAAO;EAAE,GAAG;EAAkB,GAAG;CAAK;CAE5C,OACE,oBAAC,YAAD;EAAY,GAAI;YACb,MAAM,SAAS,IAAI,UAAU,SAAS,oBAAoB,OAAO;GAChE,OAAO,eAAe,KAAK,IAAI,aAAa,OAA6B,IAAI,IAAI;EACnF,CAAC;CACS,CAAA;AAEhB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bento/focus-lock",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Focus lock primitive for managing focus within a scope",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
|
-
"module": "./dist/index.
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "
|
|
10
|
-
"lint": "biome lint &&
|
|
11
|
-
"
|
|
12
|
-
"prepublishOnly": "node ../../scripts/compile-readme.ts",
|
|
13
|
-
"pretest": "npm run build",
|
|
9
|
+
"build": "tsdown",
|
|
10
|
+
"lint": "biome lint && tsgo --noEmit",
|
|
11
|
+
"prepublishOnly": "node ../../../scripts/compile-readme.ts",
|
|
14
12
|
"test": "vitest --run",
|
|
15
|
-
"test:watch": "vitest"
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"typecheck": "tsgo --noEmit -p tsconfig.json"
|
|
16
15
|
},
|
|
17
16
|
"repository": {
|
|
18
17
|
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/godaddy/
|
|
18
|
+
"url": "git+https://github.com/godaddy/antares.git"
|
|
20
19
|
},
|
|
21
20
|
"keywords": [
|
|
22
21
|
"accessibility",
|
|
@@ -33,20 +32,20 @@
|
|
|
33
32
|
"author": "GoDaddy Operating Company, LLC",
|
|
34
33
|
"license": "MIT",
|
|
35
34
|
"bugs": {
|
|
36
|
-
"url": "https://github.com/godaddy/
|
|
35
|
+
"url": "https://github.com/godaddy/antares/issues"
|
|
37
36
|
},
|
|
38
|
-
"homepage": "https://github.com/godaddy/
|
|
37
|
+
"homepage": "https://github.com/godaddy/antares#readme",
|
|
39
38
|
"files": [
|
|
40
39
|
"dist",
|
|
41
40
|
"src",
|
|
42
41
|
"package.json"
|
|
43
42
|
],
|
|
44
43
|
"dependencies": {
|
|
45
|
-
"@bento/slots": "^0.
|
|
44
|
+
"@bento/slots": "^0.3.0",
|
|
46
45
|
"@bento/use-data-attributes": "^0.1.1",
|
|
47
|
-
"@bento/use-props": "^0.2.
|
|
48
|
-
"@react-aria/focus": "^3.
|
|
49
|
-
"@react-aria/interactions": "^3.
|
|
46
|
+
"@bento/use-props": "^0.2.3",
|
|
47
|
+
"@react-aria/focus": "^3.22.0",
|
|
48
|
+
"@react-aria/interactions": "^3.28.0"
|
|
50
49
|
},
|
|
51
50
|
"devDependencies": {
|
|
52
51
|
"@bento/button": "*",
|
|
@@ -54,7 +53,13 @@
|
|
|
54
53
|
"@bento/heading": "*",
|
|
55
54
|
"@bento/listbox": "*",
|
|
56
55
|
"@bento/radio": "*",
|
|
57
|
-
"@bento/text": "*"
|
|
56
|
+
"@bento/text": "*",
|
|
57
|
+
"@types/react": "^19.2.15",
|
|
58
|
+
"@types/react-dom": "^19.2.3",
|
|
59
|
+
"tsdown": "^0.22.1",
|
|
60
|
+
"typescript": "^6.0.3",
|
|
61
|
+
"vitest": "^4.1.7",
|
|
62
|
+
"vitest-browser-react": "^2.2.0"
|
|
58
63
|
},
|
|
59
64
|
"peerDependencies": {
|
|
60
65
|
"react": "18.x || 19.x",
|
|
@@ -63,8 +68,8 @@
|
|
|
63
68
|
"exports": {
|
|
64
69
|
".": {
|
|
65
70
|
"import": {
|
|
66
|
-
"types": "./dist/index.d.
|
|
67
|
-
"default": "./dist/index.
|
|
71
|
+
"types": "./dist/index.d.mts",
|
|
72
|
+
"default": "./dist/index.mjs"
|
|
68
73
|
},
|
|
69
74
|
"require": {
|
|
70
75
|
"types": "./dist/index.d.cts",
|
package/src/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { cloneElement, isValidElement, type ReactNode, useState } from 'react';
|
|
2
2
|
import { useDataAttributes } from '@bento/use-data-attributes';
|
|
3
3
|
import { useFocusWithin } from '@react-aria/interactions';
|
|
4
4
|
import { withSlots, type Slots } from '@bento/slots';
|
|
@@ -169,7 +169,7 @@ export const FocusLock = withSlots('BentoFocusLock', function FocusLock(args: Fo
|
|
|
169
169
|
|
|
170
170
|
return (
|
|
171
171
|
<FocusScope {...spread}>
|
|
172
|
-
{Children.map(children, function applyDataAttributes(child) {
|
|
172
|
+
{React.Children.map(children, function applyDataAttributes(child) {
|
|
173
173
|
return isValidElement(child) ? cloneElement(child as React.ReactElement, kids) : child;
|
|
174
174
|
})}
|
|
175
175
|
</FocusScope>
|
package/dist/index.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import React, { useState, Children, cloneElement, isValidElement } from 'react';
|
|
2
|
-
import { useDataAttributes } from '@bento/use-data-attributes';
|
|
3
|
-
import { useFocusWithin } from '@react-aria/interactions';
|
|
4
|
-
import { withSlots } from '@bento/slots';
|
|
5
|
-
import { FocusScope } from '@react-aria/focus';
|
|
6
|
-
import { useProps } from '@bento/use-props';
|
|
7
|
-
|
|
8
|
-
// src/index.tsx
|
|
9
|
-
var FocusLock = withSlots("BentoFocusLock", function FocusLock2(args) {
|
|
10
|
-
const { contain = false, restoreFocus = false, autoFocus = false, children, onFocusEnter, onFocusLeave } = args;
|
|
11
|
-
const [hasFocus, setHasFocus] = useState(false);
|
|
12
|
-
const { focusWithinProps } = useFocusWithin({
|
|
13
|
-
onFocusWithinChange: function onFocusWithinChange(isFocusWithin) {
|
|
14
|
-
setHasFocus(isFocusWithin);
|
|
15
|
-
},
|
|
16
|
-
onFocusWithin: onFocusEnter,
|
|
17
|
-
onBlurWithin: onFocusLeave
|
|
18
|
-
});
|
|
19
|
-
const state = {
|
|
20
|
-
hasFocus,
|
|
21
|
-
isContained: contain
|
|
22
|
-
};
|
|
23
|
-
const { apply } = useProps(args, state);
|
|
24
|
-
const data = useDataAttributes({
|
|
25
|
-
"focus-contained": contain,
|
|
26
|
-
"has-focus": hasFocus
|
|
27
|
-
});
|
|
28
|
-
if (!children) return null;
|
|
29
|
-
const spread = apply({ contain, restoreFocus, autoFocus }, ["children", "onFocusEnter", "onFocusLeave"]);
|
|
30
|
-
const kids = { ...focusWithinProps, ...data };
|
|
31
|
-
return /* @__PURE__ */ React.createElement(FocusScope, { ...spread }, Children.map(children, function applyDataAttributes(child) {
|
|
32
|
-
return isValidElement(child) ? cloneElement(child, kids) : child;
|
|
33
|
-
}));
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export { FocusLock };
|
|
37
|
-
//# sourceMappingURL=index.js.map
|
|
38
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.tsx"],"names":["FocusLock"],"mappings":";;;;;;;;AAiIO,IAAM,SAAA,GAAY,SAAA,CAAU,gBAAA,EAAkB,SAASA,WAAU,IAAA,EAAsB;AAC5F,EAAA,MAAM,EAAE,OAAA,GAAU,KAAA,EAAO,YAAA,GAAe,KAAA,EAAO,YAAY,KAAA,EAAO,QAAA,EAAU,YAAA,EAAc,YAAA,EAAa,GAAI,IAAA;AAC3G,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAG9C,EAAA,MAAM,EAAE,gBAAA,EAAiB,GAAI,cAAA,CAAe;AAAA,IAC1C,mBAAA,EAAqB,SAAS,mBAAA,CAAoB,aAAA,EAAe;AAC/D,MAAA,WAAA,CAAY,aAAa,CAAA;AAAA,IAC3B,CAAA;AAAA,IACA,aAAA,EAAe,YAAA;AAAA,IACf,YAAA,EAAc;AAAA,GACf,CAAA;AAGD,EAAA,MAAM,KAAA,GAAwB;AAAA,IAC5B,QAAA;AAAA,IACA,WAAA,EAAa;AAAA,GACf;AAGA,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,QAAA,CAAS,MAAM,KAAK,CAAA;AAGtC,EAAA,MAAM,OAAO,iBAAA,CAAkB;AAAA,IAC7B,iBAAA,EAAmB,OAAA;AAAA,IACnB,WAAA,EAAa;AAAA,GACd,CAAA;AAED,EAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AAKtB,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,EAAE,OAAA,EAAS,YAAA,EAAc,SAAA,EAAU,EAAG,CAAC,UAAA,EAAY,cAAA,EAAgB,cAAc,CAAC,CAAA;AAKvG,EAAA,MAAM,IAAA,GAAO,EAAE,GAAG,gBAAA,EAAkB,GAAG,IAAA,EAAK;AAE5C,EAAA,uBACE,KAAA,CAAA,aAAA,CAAC,cAAY,GAAG,MAAA,EAAA,EACb,SAAS,GAAA,CAAI,QAAA,EAAU,SAAS,mBAAA,CAAoB,KAAA,EAAO;AAC1D,IAAA,OAAO,eAAe,KAAK,CAAA,GAAI,YAAA,CAAa,KAAA,EAA6B,IAAI,CAAA,GAAI,KAAA;AAAA,EACnF,CAAC,CACH,CAAA;AAEJ,CAAC","file":"index.js","sourcesContent":["import React, { Children, cloneElement, isValidElement, type ReactNode, useState } from 'react';\nimport { useDataAttributes } from '@bento/use-data-attributes';\nimport { useFocusWithin } from '@react-aria/interactions';\nimport { withSlots, type Slots } from '@bento/slots';\nimport { FocusScope } from '@react-aria/focus';\nimport { useProps } from '@bento/use-props';\n\n/**\n * State object passed to render prop functions\n * @public\n */\nexport interface FocusLockState {\n /**\n * Whether focus is currently within the scope\n */\n hasFocus: boolean;\n\n /**\n * Whether focus is contained within the scope\n */\n isContained: boolean;\n}\n\nexport interface FocusLockProps extends Slots {\n /**\n * Whether to contain focus within the scope.\n * When true, focus will cycle between focusable elements within the scope.\n *\n * @default false\n */\n contain?: boolean;\n\n /**\n * Whether to restore focus to the previously focused element when the focus scope unmounts.\n *\n * @default false\n */\n restoreFocus?: boolean;\n\n /**\n * Whether to automatically focus the first focusable element when the focus scope mounts.\n *\n * @default false\n */\n autoFocus?: boolean;\n\n /**\n * The content to render inside the focus lock.\n * Can be a single element or multiple elements.\n */\n children?: ReactNode;\n\n /**\n * Callback fired when focus enters the scope\n */\n onFocusEnter?: (e: React.FocusEvent) => void;\n\n /**\n * Callback fired when focus leaves the scope\n */\n onFocusLeave?: (e: React.FocusEvent) => void;\n\n /**\n * Render prop for className\n */\n className?: ((state: FocusLockState) => string) | string;\n\n /**\n * Render prop for style\n */\n style?: ((state: FocusLockState) => React.CSSProperties) | React.CSSProperties;\n}\n\n/**\n * FocusLock manages focus within a scope, preventing focus from escaping and optionally\n * restoring focus when the scope is removed. Built on top of React ARIA's FocusScope.\n *\n * The FocusLock primitive provides essential focus management for modals, dialogs, drawers,\n * select popovers, and other overlay components that need to trap focus within their boundaries.\n *\n * This component does not add any wrapper elements - it applies data attributes directly to\n * its children, allowing for flexible composition with multiple elements or single elements.\n *\n * @component\n * @param args - The properties {@link FocusLockProps} passed to the FocusLock component.\n *\n * @example\n * ```tsx\n * // Overlay with multiple children (backdrop + content)\n * <FocusLock contain restoreFocus autoFocus>\n * <div slot=\"backdrop\" />\n * <div slot=\"content\">\n * <h2>Modal Title</h2>\n * <button>Close</button>\n * </div>\n * </FocusLock>\n * ```\n *\n * @example\n * ```tsx\n * // Select popover with single child\n * <FocusLock contain restoreFocus autoFocus>\n * <div className=\"popover\">\n * <ListBox>\n * <ListBoxItem>Option 1</ListBoxItem>\n * <ListBoxItem>Option 2</ListBoxItem>\n * </ListBox>\n * </div>\n * </FocusLock>\n * ```\n *\n * @example\n * ```tsx\n * // With render props for dynamic styling\n * <FocusLock\n * contain\n * className={({ hasFocus, isContained }) =>\n * `modal ${isContained ? 'contained' : ''} ${hasFocus ? 'focused' : ''}`\n * }\n * style={({ hasFocus }) => ({\n * opacity: hasFocus ? 1 : 0.8\n * })}\n * >\n * <div>Content</div>\n * </FocusLock>\n * ```\n *\n * @public\n */\nexport const FocusLock = withSlots('BentoFocusLock', function FocusLock(args: FocusLockProps) {\n const { contain = false, restoreFocus = false, autoFocus = false, children, onFocusEnter, onFocusLeave } = args;\n const [hasFocus, setHasFocus] = useState(false);\n\n // Track focus within the scope using React ARIA\n const { focusWithinProps } = useFocusWithin({\n onFocusWithinChange: function onFocusWithinChange(isFocusWithin) {\n setHasFocus(isFocusWithin);\n },\n onFocusWithin: onFocusEnter,\n onBlurWithin: onFocusLeave\n });\n\n // Create state object for render props\n const state: FocusLockState = {\n hasFocus,\n isContained: contain\n };\n\n // Pass state to useProps so render props can access it\n const { apply } = useProps(args, state);\n\n // Generate data attributes for focus lock state\n const data = useDataAttributes({\n 'focus-contained': contain,\n 'has-focus': hasFocus\n });\n\n if (!children) return null;\n\n //\n // Apply props to FocusScope for slot inheritance\n //\n const spread = apply({ contain, restoreFocus, autoFocus }, ['children', 'onFocusEnter', 'onFocusLeave']);\n\n //\n // Merge focus tracking props with data attributes to apply to children\n //\n const kids = { ...focusWithinProps, ...data };\n\n return (\n <FocusScope {...spread}>\n {Children.map(children, function applyDataAttributes(child) {\n return isValidElement(child) ? cloneElement(child as React.ReactElement, kids) : child;\n })}\n </FocusScope>\n );\n}) as (props: FocusLockProps) => React.ReactElement | null;\n"]}
|