@jobber/components 7.10.0 → 7.11.0
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/dist/Autocomplete/index.cjs +1 -0
- package/dist/Autocomplete/index.mjs +1 -0
- package/dist/Card/index.cjs +3 -0
- package/dist/Card/index.mjs +3 -0
- package/dist/Chip/index.cjs +1 -0
- package/dist/Chip/index.mjs +1 -0
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +1 -0
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +1 -0
- package/dist/Chips/InternalChipDismissible/index.cjs +1 -0
- package/dist/Chips/InternalChipDismissible/index.mjs +1 -0
- package/dist/Chips/index.cjs +1 -0
- package/dist/Chips/index.mjs +1 -0
- package/dist/Combobox/components/ComboboxActivator/index.cjs +1 -0
- package/dist/Combobox/components/ComboboxActivator/index.mjs +1 -0
- package/dist/Combobox/components/ComboboxContent/index.cjs +1 -0
- package/dist/Combobox/components/ComboboxContent/index.mjs +1 -0
- package/dist/Combobox/components/ComboboxTrigger/index.cjs +1 -0
- package/dist/Combobox/components/ComboboxTrigger/index.mjs +1 -0
- package/dist/Combobox/index.cjs +1 -0
- package/dist/Combobox/index.mjs +1 -0
- package/dist/ConfirmationModal/index.cjs +1 -0
- package/dist/ConfirmationModal/index.mjs +1 -0
- package/dist/DataDump/index.cjs +3 -0
- package/dist/DataDump/index.mjs +3 -0
- package/dist/DataList/components/DataListActions/index.cjs +1 -0
- package/dist/DataList/components/DataListActions/index.mjs +1 -0
- package/dist/DataList/components/DataListBulkActions/index.cjs +1 -0
- package/dist/DataList/components/DataListBulkActions/index.mjs +1 -0
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +1 -0
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +1 -0
- package/dist/DataList/components/DataListFilters/index.cjs +1 -0
- package/dist/DataList/components/DataListFilters/index.mjs +1 -0
- package/dist/DataList/components/DataListHeader/index.cjs +1 -0
- package/dist/DataList/components/DataListHeader/index.mjs +1 -0
- package/dist/DataList/components/DataListItem/index.cjs +1 -0
- package/dist/DataList/components/DataListItem/index.mjs +1 -0
- package/dist/DataList/components/DataListItemActions/index.cjs +1 -0
- package/dist/DataList/components/DataListItemActions/index.mjs +1 -0
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -0
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -0
- package/dist/DataList/components/DataListItems/index.cjs +1 -0
- package/dist/DataList/components/DataListItems/index.mjs +1 -0
- package/dist/DataList/components/DataListLayout/index.cjs +1 -0
- package/dist/DataList/components/DataListLayout/index.mjs +1 -0
- package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -0
- package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -0
- package/dist/DataList/index.cjs +1 -0
- package/dist/DataList/index.mjs +1 -0
- package/dist/DatePicker/index.cjs +1 -0
- package/dist/DatePicker/index.mjs +1 -0
- package/dist/DrawerRoot-cjs.js +181 -968
- package/dist/DrawerRoot-es.js +5 -734
- package/dist/FormatFile/index.cjs +1 -0
- package/dist/FormatFile/index.mjs +1 -0
- package/dist/Gallery/index.cjs +1 -0
- package/dist/Gallery/index.mjs +1 -0
- package/dist/InputDate/index.cjs +1 -0
- package/dist/InputDate/index.mjs +1 -0
- package/dist/InputNumberExperimental-cjs.js +783 -0
- package/dist/InputNumberExperimental-es.js +763 -0
- package/dist/LightBox/index.cjs +1 -0
- package/dist/LightBox/index.mjs +1 -0
- package/dist/Menu/index.cjs +3 -0
- package/dist/Menu/index.mjs +3 -0
- package/dist/MenuSubmenuTrigger-cjs.js +202 -447
- package/dist/MenuSubmenuTrigger-es.js +7 -249
- package/dist/Modal/index.cjs +1 -0
- package/dist/Modal/index.mjs +1 -0
- package/dist/NumberFieldInput-cjs.js +1828 -0
- package/dist/NumberFieldInput-es.js +1788 -0
- package/dist/Page/index.cjs +3 -0
- package/dist/Page/index.mjs +3 -0
- package/dist/Popover/index.cjs +1 -0
- package/dist/Popover/index.mjs +1 -0
- package/dist/Tooltip/index.cjs +1 -0
- package/dist/Tooltip/index.mjs +1 -0
- package/dist/docs/Menu/Menu.md +197 -37
- package/dist/floating-ui.react-cjs.js +35 -34
- package/dist/floating-ui.react-dom-cjs.js +65 -64
- package/dist/floating-ui.react-dom-es.js +2 -1
- package/dist/floating-ui.react-es.js +2 -1
- package/dist/floating-ui.utils.dom-cjs.js +185 -0
- package/dist/floating-ui.utils.dom-es.js +165 -0
- package/dist/index.cjs +3 -0
- package/dist/index.esm-cjs.js +0 -183
- package/dist/index.esm-es.js +1 -165
- package/dist/index.mjs +3 -0
- package/dist/primitives/BottomSheet/index.cjs +3 -1
- package/dist/primitives/BottomSheet/index.mjs +3 -1
- package/dist/primitives/InputNumberExperimental/InputNumberExperimental.d.ts +20 -0
- package/dist/primitives/InputNumberExperimental/index.cjs +22 -0
- package/dist/primitives/InputNumberExperimental/index.d.ts +2 -0
- package/dist/primitives/InputNumberExperimental/index.mjs +16 -0
- package/dist/primitives/InputNumberExperimental/types.d.ts +147 -0
- package/dist/primitives/index.cjs +9 -1
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.mjs +8 -1
- package/dist/styles.css +499 -0
- package/dist/unstyledPrimitives/index.cjs +264 -2039
- package/dist/unstyledPrimitives/index.mjs +72 -1847
- package/dist/useBaseUiId-cjs.js +275 -0
- package/dist/useBaseUiId-es.js +251 -0
- package/dist/useValueChanged-cjs.js +820 -0
- package/dist/useValueChanged-es.js +736 -0
- package/package.json +2 -2
- package/rollup.config.mjs +13 -2
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
|
|
5
|
+
var useValueChanged = require('./useValueChanged-cjs.js');
|
|
6
|
+
var useRenderElement = require('./useRenderElement-cjs.js');
|
|
7
|
+
|
|
8
|
+
function _interopNamespaceDefault(e) {
|
|
9
|
+
var n = Object.create(null);
|
|
10
|
+
if (e) {
|
|
11
|
+
Object.keys(e).forEach(function (k) {
|
|
12
|
+
if (k !== 'default') {
|
|
13
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return e[k]; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
|
26
|
+
|
|
27
|
+
let set;
|
|
28
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
29
|
+
set = new Set();
|
|
30
|
+
}
|
|
31
|
+
function error(...messages) {
|
|
32
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
33
|
+
const messageKey = messages.join(' ');
|
|
34
|
+
if (!set.has(messageKey)) {
|
|
35
|
+
set.add(messageKey);
|
|
36
|
+
console.error(`Base UI: ${messageKey}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const CompositeRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
|
|
42
|
+
if (process.env.NODE_ENV !== "production") CompositeRootContext.displayName = "CompositeRootContext";
|
|
43
|
+
function useCompositeRootContext(optional = false) {
|
|
44
|
+
const context = React__namespace.useContext(CompositeRootContext);
|
|
45
|
+
if (context === undefined && !optional) {
|
|
46
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: CompositeRootContext is missing. Composite parts must be placed within <Composite.Root>.' : useRenderElement.formatErrorMessage(16));
|
|
47
|
+
}
|
|
48
|
+
return context;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function useFocusableWhenDisabled(parameters) {
|
|
52
|
+
const {
|
|
53
|
+
focusableWhenDisabled,
|
|
54
|
+
disabled,
|
|
55
|
+
composite = false,
|
|
56
|
+
tabIndex: tabIndexProp = 0,
|
|
57
|
+
isNativeButton
|
|
58
|
+
} = parameters;
|
|
59
|
+
const isFocusableComposite = composite && focusableWhenDisabled !== false;
|
|
60
|
+
const isNonFocusableComposite = composite && focusableWhenDisabled === false;
|
|
61
|
+
|
|
62
|
+
// we can't explicitly assign `undefined` to any of these props because it
|
|
63
|
+
// would otherwise prevent subsequently merged props from setting them
|
|
64
|
+
const props = React__namespace.useMemo(() => {
|
|
65
|
+
const additionalProps = {
|
|
66
|
+
// allow Tabbing away from focusableWhenDisabled elements
|
|
67
|
+
onKeyDown(event) {
|
|
68
|
+
if (disabled && focusableWhenDisabled && event.key !== 'Tab') {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
if (!composite) {
|
|
74
|
+
additionalProps.tabIndex = tabIndexProp;
|
|
75
|
+
if (!isNativeButton && disabled) {
|
|
76
|
+
additionalProps.tabIndex = focusableWhenDisabled ? tabIndexProp : -1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (isNativeButton && (focusableWhenDisabled || isFocusableComposite) || !isNativeButton && disabled) {
|
|
80
|
+
additionalProps['aria-disabled'] = disabled;
|
|
81
|
+
}
|
|
82
|
+
if (isNativeButton && (!focusableWhenDisabled || isNonFocusableComposite)) {
|
|
83
|
+
additionalProps.disabled = disabled;
|
|
84
|
+
}
|
|
85
|
+
return additionalProps;
|
|
86
|
+
}, [composite, disabled, focusableWhenDisabled, isFocusableComposite, isNonFocusableComposite, isNativeButton, tabIndexProp]);
|
|
87
|
+
return {
|
|
88
|
+
props
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function useButton(parameters = {}) {
|
|
93
|
+
const {
|
|
94
|
+
disabled = false,
|
|
95
|
+
focusableWhenDisabled,
|
|
96
|
+
tabIndex = 0,
|
|
97
|
+
native: isNativeButton = true,
|
|
98
|
+
composite: compositeProp
|
|
99
|
+
} = parameters;
|
|
100
|
+
const elementRef = React__namespace.useRef(null);
|
|
101
|
+
const compositeRootContext = useCompositeRootContext(true);
|
|
102
|
+
const isCompositeItem = compositeProp ?? compositeRootContext !== undefined;
|
|
103
|
+
const {
|
|
104
|
+
props: focusableWhenDisabledProps
|
|
105
|
+
} = useFocusableWhenDisabled({
|
|
106
|
+
focusableWhenDisabled,
|
|
107
|
+
disabled,
|
|
108
|
+
composite: isCompositeItem,
|
|
109
|
+
tabIndex,
|
|
110
|
+
isNativeButton
|
|
111
|
+
});
|
|
112
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
113
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
114
|
+
React__namespace.useEffect(() => {
|
|
115
|
+
if (!elementRef.current) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const isButtonTag = isButtonElement(elementRef.current);
|
|
119
|
+
if (isNativeButton) {
|
|
120
|
+
if (!isButtonTag) {
|
|
121
|
+
const ownerStackMessage = useValueChanged.SafeReact.captureOwnerStack?.() || '';
|
|
122
|
+
const message = 'A component that acts as a button expected a native <button> because the ' + '`nativeButton` prop is true. Rendering a non-<button> removes native button ' + 'semantics, which can impact forms and accessibility. Use a real <button> in the ' + '`render` prop, or set `nativeButton` to `false`.';
|
|
123
|
+
error(`${message}${ownerStackMessage}`);
|
|
124
|
+
}
|
|
125
|
+
} else if (isButtonTag) {
|
|
126
|
+
const ownerStackMessage = useValueChanged.SafeReact.captureOwnerStack?.() || '';
|
|
127
|
+
const message = 'A component that acts as a button expected a non-<button> because the `nativeButton` ' + 'prop is false. Rendering a <button> keeps native behavior while Base UI applies ' + 'non-native attributes and handlers, which can add unintended extra attributes (such ' + 'as `role` or `aria-disabled`). Use a non-<button> in the `render` prop, or set ' + '`nativeButton` to `true`.';
|
|
128
|
+
error(`${message}${ownerStackMessage}`);
|
|
129
|
+
}
|
|
130
|
+
}, [isNativeButton]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// handles a disabled composite button rendering another button, e.g.
|
|
134
|
+
// <Toolbar.Button disabled render={<Menu.Trigger />} />
|
|
135
|
+
// the `disabled` prop needs to pass through 2 `useButton`s then finally
|
|
136
|
+
// delete the `disabled` attribute from DOM
|
|
137
|
+
const updateDisabled = React__namespace.useCallback(() => {
|
|
138
|
+
const element = elementRef.current;
|
|
139
|
+
if (!isButtonElement(element)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (isCompositeItem && disabled && focusableWhenDisabledProps.disabled === undefined && element.disabled) {
|
|
143
|
+
element.disabled = false;
|
|
144
|
+
}
|
|
145
|
+
}, [disabled, focusableWhenDisabledProps.disabled, isCompositeItem]);
|
|
146
|
+
useValueChanged.useIsoLayoutEffect(updateDisabled, [updateDisabled]);
|
|
147
|
+
const getButtonProps = React__namespace.useCallback((externalProps = {}) => {
|
|
148
|
+
const {
|
|
149
|
+
onClick: externalOnClick,
|
|
150
|
+
onMouseDown: externalOnMouseDown,
|
|
151
|
+
onKeyUp: externalOnKeyUp,
|
|
152
|
+
onKeyDown: externalOnKeyDown,
|
|
153
|
+
onPointerDown: externalOnPointerDown,
|
|
154
|
+
...otherExternalProps
|
|
155
|
+
} = externalProps;
|
|
156
|
+
const type = isNativeButton ? 'button' : undefined;
|
|
157
|
+
return useRenderElement.mergeProps({
|
|
158
|
+
type,
|
|
159
|
+
onClick(event) {
|
|
160
|
+
if (disabled) {
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
externalOnClick?.(event);
|
|
165
|
+
},
|
|
166
|
+
onMouseDown(event) {
|
|
167
|
+
if (!disabled) {
|
|
168
|
+
externalOnMouseDown?.(event);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
onKeyDown(event) {
|
|
172
|
+
if (disabled) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
useRenderElement.makeEventPreventable(event);
|
|
176
|
+
externalOnKeyDown?.(event);
|
|
177
|
+
if (event.baseUIHandlerPrevented) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const isCurrentTarget = event.target === event.currentTarget;
|
|
181
|
+
const currentTarget = event.currentTarget;
|
|
182
|
+
const isButton = isButtonElement(currentTarget);
|
|
183
|
+
const isLink = !isNativeButton && isValidLinkElement(currentTarget);
|
|
184
|
+
const shouldClick = isCurrentTarget && (isNativeButton ? isButton : !isLink);
|
|
185
|
+
const isEnterKey = event.key === 'Enter';
|
|
186
|
+
const isSpaceKey = event.key === ' ';
|
|
187
|
+
const role = currentTarget.getAttribute('role');
|
|
188
|
+
const isTextNavigationRole = role?.startsWith('menuitem') || role === 'option' || role === 'gridcell';
|
|
189
|
+
if (isCurrentTarget && isCompositeItem && isSpaceKey) {
|
|
190
|
+
if (event.defaultPrevented && isTextNavigationRole) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
event.preventDefault();
|
|
194
|
+
if (isLink || isNativeButton && isButton) {
|
|
195
|
+
currentTarget.click();
|
|
196
|
+
event.preventBaseUIHandler();
|
|
197
|
+
} else if (shouldClick) {
|
|
198
|
+
externalOnClick?.(event);
|
|
199
|
+
event.preventBaseUIHandler();
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Keyboard accessibility for native and non-native elements.
|
|
205
|
+
if (shouldClick) {
|
|
206
|
+
if (!isNativeButton && (isSpaceKey || isEnterKey)) {
|
|
207
|
+
event.preventDefault();
|
|
208
|
+
}
|
|
209
|
+
if (!isNativeButton && isEnterKey) {
|
|
210
|
+
externalOnClick?.(event);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
onKeyUp(event) {
|
|
215
|
+
if (disabled) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
|
|
220
|
+
// https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
|
|
221
|
+
useRenderElement.makeEventPreventable(event);
|
|
222
|
+
externalOnKeyUp?.(event);
|
|
223
|
+
if (event.target === event.currentTarget && isNativeButton && isCompositeItem && isButtonElement(event.currentTarget) && event.key === ' ') {
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (event.baseUIHandlerPrevented) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Keyboard accessibility for non interactive elements
|
|
232
|
+
if (event.target === event.currentTarget && !isNativeButton && !isCompositeItem && event.key === ' ') {
|
|
233
|
+
externalOnClick?.(event);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
onPointerDown(event) {
|
|
237
|
+
if (disabled) {
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
externalOnPointerDown?.(event);
|
|
242
|
+
}
|
|
243
|
+
}, !isNativeButton ? {
|
|
244
|
+
role: 'button'
|
|
245
|
+
} : undefined, focusableWhenDisabledProps, otherExternalProps);
|
|
246
|
+
}, [disabled, focusableWhenDisabledProps, isCompositeItem, isNativeButton]);
|
|
247
|
+
const buttonRef = useValueChanged.useStableCallback(element => {
|
|
248
|
+
elementRef.current = element;
|
|
249
|
+
updateDisabled();
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
getButtonProps,
|
|
253
|
+
buttonRef
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function isButtonElement(elem) {
|
|
257
|
+
return floatingUi_utils_dom.isHTMLElement(elem) && elem.tagName === 'BUTTON';
|
|
258
|
+
}
|
|
259
|
+
function isValidLinkElement(elem) {
|
|
260
|
+
return Boolean(elem?.tagName === 'A' && elem?.href);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Wraps `useId` and prefixes generated `id`s with `base-ui-`
|
|
265
|
+
* @param {string | undefined} idOverride overrides the generated id when provided
|
|
266
|
+
* @returns {string | undefined}
|
|
267
|
+
*/
|
|
268
|
+
function useBaseUiId(idOverride) {
|
|
269
|
+
return useValueChanged.useId(idOverride, 'base-ui');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
exports.error = error;
|
|
273
|
+
exports.useBaseUiId = useBaseUiId;
|
|
274
|
+
exports.useButton = useButton;
|
|
275
|
+
exports.useCompositeRootContext = useCompositeRootContext;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { a as isHTMLElement } from './floating-ui.utils.dom-es.js';
|
|
3
|
+
import { S as SafeReact, u as useIsoLayoutEffect, a as useStableCallback, b as useId } from './useValueChanged-es.js';
|
|
4
|
+
import { f as formatErrorMessage, m as mergeProps, a as makeEventPreventable } from './useRenderElement-es.js';
|
|
5
|
+
|
|
6
|
+
let set;
|
|
7
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
8
|
+
set = new Set();
|
|
9
|
+
}
|
|
10
|
+
function error(...messages) {
|
|
11
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
12
|
+
const messageKey = messages.join(' ');
|
|
13
|
+
if (!set.has(messageKey)) {
|
|
14
|
+
set.add(messageKey);
|
|
15
|
+
console.error(`Base UI: ${messageKey}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const CompositeRootContext = /*#__PURE__*/React.createContext(undefined);
|
|
21
|
+
if (process.env.NODE_ENV !== "production") CompositeRootContext.displayName = "CompositeRootContext";
|
|
22
|
+
function useCompositeRootContext(optional = false) {
|
|
23
|
+
const context = React.useContext(CompositeRootContext);
|
|
24
|
+
if (context === undefined && !optional) {
|
|
25
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: CompositeRootContext is missing. Composite parts must be placed within <Composite.Root>.' : formatErrorMessage(16));
|
|
26
|
+
}
|
|
27
|
+
return context;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function useFocusableWhenDisabled(parameters) {
|
|
31
|
+
const {
|
|
32
|
+
focusableWhenDisabled,
|
|
33
|
+
disabled,
|
|
34
|
+
composite = false,
|
|
35
|
+
tabIndex: tabIndexProp = 0,
|
|
36
|
+
isNativeButton
|
|
37
|
+
} = parameters;
|
|
38
|
+
const isFocusableComposite = composite && focusableWhenDisabled !== false;
|
|
39
|
+
const isNonFocusableComposite = composite && focusableWhenDisabled === false;
|
|
40
|
+
|
|
41
|
+
// we can't explicitly assign `undefined` to any of these props because it
|
|
42
|
+
// would otherwise prevent subsequently merged props from setting them
|
|
43
|
+
const props = React.useMemo(() => {
|
|
44
|
+
const additionalProps = {
|
|
45
|
+
// allow Tabbing away from focusableWhenDisabled elements
|
|
46
|
+
onKeyDown(event) {
|
|
47
|
+
if (disabled && focusableWhenDisabled && event.key !== 'Tab') {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
if (!composite) {
|
|
53
|
+
additionalProps.tabIndex = tabIndexProp;
|
|
54
|
+
if (!isNativeButton && disabled) {
|
|
55
|
+
additionalProps.tabIndex = focusableWhenDisabled ? tabIndexProp : -1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (isNativeButton && (focusableWhenDisabled || isFocusableComposite) || !isNativeButton && disabled) {
|
|
59
|
+
additionalProps['aria-disabled'] = disabled;
|
|
60
|
+
}
|
|
61
|
+
if (isNativeButton && (!focusableWhenDisabled || isNonFocusableComposite)) {
|
|
62
|
+
additionalProps.disabled = disabled;
|
|
63
|
+
}
|
|
64
|
+
return additionalProps;
|
|
65
|
+
}, [composite, disabled, focusableWhenDisabled, isFocusableComposite, isNonFocusableComposite, isNativeButton, tabIndexProp]);
|
|
66
|
+
return {
|
|
67
|
+
props
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function useButton(parameters = {}) {
|
|
72
|
+
const {
|
|
73
|
+
disabled = false,
|
|
74
|
+
focusableWhenDisabled,
|
|
75
|
+
tabIndex = 0,
|
|
76
|
+
native: isNativeButton = true,
|
|
77
|
+
composite: compositeProp
|
|
78
|
+
} = parameters;
|
|
79
|
+
const elementRef = React.useRef(null);
|
|
80
|
+
const compositeRootContext = useCompositeRootContext(true);
|
|
81
|
+
const isCompositeItem = compositeProp ?? compositeRootContext !== undefined;
|
|
82
|
+
const {
|
|
83
|
+
props: focusableWhenDisabledProps
|
|
84
|
+
} = useFocusableWhenDisabled({
|
|
85
|
+
focusableWhenDisabled,
|
|
86
|
+
disabled,
|
|
87
|
+
composite: isCompositeItem,
|
|
88
|
+
tabIndex,
|
|
89
|
+
isNativeButton
|
|
90
|
+
});
|
|
91
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
92
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
if (!elementRef.current) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const isButtonTag = isButtonElement(elementRef.current);
|
|
98
|
+
if (isNativeButton) {
|
|
99
|
+
if (!isButtonTag) {
|
|
100
|
+
const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
|
|
101
|
+
const message = 'A component that acts as a button expected a native <button> because the ' + '`nativeButton` prop is true. Rendering a non-<button> removes native button ' + 'semantics, which can impact forms and accessibility. Use a real <button> in the ' + '`render` prop, or set `nativeButton` to `false`.';
|
|
102
|
+
error(`${message}${ownerStackMessage}`);
|
|
103
|
+
}
|
|
104
|
+
} else if (isButtonTag) {
|
|
105
|
+
const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
|
|
106
|
+
const message = 'A component that acts as a button expected a non-<button> because the `nativeButton` ' + 'prop is false. Rendering a <button> keeps native behavior while Base UI applies ' + 'non-native attributes and handlers, which can add unintended extra attributes (such ' + 'as `role` or `aria-disabled`). Use a non-<button> in the `render` prop, or set ' + '`nativeButton` to `true`.';
|
|
107
|
+
error(`${message}${ownerStackMessage}`);
|
|
108
|
+
}
|
|
109
|
+
}, [isNativeButton]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// handles a disabled composite button rendering another button, e.g.
|
|
113
|
+
// <Toolbar.Button disabled render={<Menu.Trigger />} />
|
|
114
|
+
// the `disabled` prop needs to pass through 2 `useButton`s then finally
|
|
115
|
+
// delete the `disabled` attribute from DOM
|
|
116
|
+
const updateDisabled = React.useCallback(() => {
|
|
117
|
+
const element = elementRef.current;
|
|
118
|
+
if (!isButtonElement(element)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (isCompositeItem && disabled && focusableWhenDisabledProps.disabled === undefined && element.disabled) {
|
|
122
|
+
element.disabled = false;
|
|
123
|
+
}
|
|
124
|
+
}, [disabled, focusableWhenDisabledProps.disabled, isCompositeItem]);
|
|
125
|
+
useIsoLayoutEffect(updateDisabled, [updateDisabled]);
|
|
126
|
+
const getButtonProps = React.useCallback((externalProps = {}) => {
|
|
127
|
+
const {
|
|
128
|
+
onClick: externalOnClick,
|
|
129
|
+
onMouseDown: externalOnMouseDown,
|
|
130
|
+
onKeyUp: externalOnKeyUp,
|
|
131
|
+
onKeyDown: externalOnKeyDown,
|
|
132
|
+
onPointerDown: externalOnPointerDown,
|
|
133
|
+
...otherExternalProps
|
|
134
|
+
} = externalProps;
|
|
135
|
+
const type = isNativeButton ? 'button' : undefined;
|
|
136
|
+
return mergeProps({
|
|
137
|
+
type,
|
|
138
|
+
onClick(event) {
|
|
139
|
+
if (disabled) {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
externalOnClick?.(event);
|
|
144
|
+
},
|
|
145
|
+
onMouseDown(event) {
|
|
146
|
+
if (!disabled) {
|
|
147
|
+
externalOnMouseDown?.(event);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
onKeyDown(event) {
|
|
151
|
+
if (disabled) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
makeEventPreventable(event);
|
|
155
|
+
externalOnKeyDown?.(event);
|
|
156
|
+
if (event.baseUIHandlerPrevented) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const isCurrentTarget = event.target === event.currentTarget;
|
|
160
|
+
const currentTarget = event.currentTarget;
|
|
161
|
+
const isButton = isButtonElement(currentTarget);
|
|
162
|
+
const isLink = !isNativeButton && isValidLinkElement(currentTarget);
|
|
163
|
+
const shouldClick = isCurrentTarget && (isNativeButton ? isButton : !isLink);
|
|
164
|
+
const isEnterKey = event.key === 'Enter';
|
|
165
|
+
const isSpaceKey = event.key === ' ';
|
|
166
|
+
const role = currentTarget.getAttribute('role');
|
|
167
|
+
const isTextNavigationRole = role?.startsWith('menuitem') || role === 'option' || role === 'gridcell';
|
|
168
|
+
if (isCurrentTarget && isCompositeItem && isSpaceKey) {
|
|
169
|
+
if (event.defaultPrevented && isTextNavigationRole) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
if (isLink || isNativeButton && isButton) {
|
|
174
|
+
currentTarget.click();
|
|
175
|
+
event.preventBaseUIHandler();
|
|
176
|
+
} else if (shouldClick) {
|
|
177
|
+
externalOnClick?.(event);
|
|
178
|
+
event.preventBaseUIHandler();
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Keyboard accessibility for native and non-native elements.
|
|
184
|
+
if (shouldClick) {
|
|
185
|
+
if (!isNativeButton && (isSpaceKey || isEnterKey)) {
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
}
|
|
188
|
+
if (!isNativeButton && isEnterKey) {
|
|
189
|
+
externalOnClick?.(event);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
onKeyUp(event) {
|
|
194
|
+
if (disabled) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
|
|
199
|
+
// https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
|
|
200
|
+
makeEventPreventable(event);
|
|
201
|
+
externalOnKeyUp?.(event);
|
|
202
|
+
if (event.target === event.currentTarget && isNativeButton && isCompositeItem && isButtonElement(event.currentTarget) && event.key === ' ') {
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (event.baseUIHandlerPrevented) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Keyboard accessibility for non interactive elements
|
|
211
|
+
if (event.target === event.currentTarget && !isNativeButton && !isCompositeItem && event.key === ' ') {
|
|
212
|
+
externalOnClick?.(event);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
onPointerDown(event) {
|
|
216
|
+
if (disabled) {
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
externalOnPointerDown?.(event);
|
|
221
|
+
}
|
|
222
|
+
}, !isNativeButton ? {
|
|
223
|
+
role: 'button'
|
|
224
|
+
} : undefined, focusableWhenDisabledProps, otherExternalProps);
|
|
225
|
+
}, [disabled, focusableWhenDisabledProps, isCompositeItem, isNativeButton]);
|
|
226
|
+
const buttonRef = useStableCallback(element => {
|
|
227
|
+
elementRef.current = element;
|
|
228
|
+
updateDisabled();
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
getButtonProps,
|
|
232
|
+
buttonRef
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function isButtonElement(elem) {
|
|
236
|
+
return isHTMLElement(elem) && elem.tagName === 'BUTTON';
|
|
237
|
+
}
|
|
238
|
+
function isValidLinkElement(elem) {
|
|
239
|
+
return Boolean(elem?.tagName === 'A' && elem?.href);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Wraps `useId` and prefixes generated `id`s with `base-ui-`
|
|
244
|
+
* @param {string | undefined} idOverride overrides the generated id when provided
|
|
245
|
+
* @returns {string | undefined}
|
|
246
|
+
*/
|
|
247
|
+
function useBaseUiId(idOverride) {
|
|
248
|
+
return useId(idOverride, 'base-ui');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { useButton as a, useCompositeRootContext as b, error as e, useBaseUiId as u };
|