first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
@@ -0,0 +1,6 @@
import * as React from 'react';
import type { IPopupProps } from './Popup.types';
/**
* This adds accessibility to Dialog and Panel controls
*/
export declare const Popup: React.FunctionComponent<IPopupProps>;
+136
View File
@@ -0,0 +1,136 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Popup = void 0;
var tslib_1 = require("tslib");
var React = require("react");
var Utilities_1 = require("../../Utilities");
var react_hooks_1 = require("@fluentui/react-hooks");
var react_window_provider_1 = require("@fluentui/react-window-provider");
function useScrollbarAsync(props, root) {
var async = (0, react_hooks_1.useAsync)();
var _a = React.useState(false), needsVerticalScrollBarState = _a[0], setNeedsVerticalScrollBar = _a[1];
React.useEffect(function () {
async.requestAnimationFrame(function () {
var _a;
// If overflowY is overridden, don't waste time calculating whether the scrollbar is necessary.
if (props.style && props.style.overflowY) {
return;
}
var needsVerticalScrollBar = false;
if (root && root.current && ((_a = root.current) === null || _a === void 0 ? void 0 : _a.firstElementChild)) {
// ClientHeight returns the client height of an element rounded to an
// integer. On some browsers at different zoom levels this rounding
// can generate different results for the root container and child even
// though they are the same height. This causes us to show a scroll bar
// when not needed. Ideally we would use BoundingClientRect().height
// instead however seems that the API is 90% slower than using ClientHeight.
// Therefore instead we will calculate the difference between heights and
// allow for a 1px difference to still be considered ok and not show the
// scroll bar.
var rootHeight = root.current.clientHeight;
var firstChildHeight = root.current.firstElementChild.clientHeight;
if (rootHeight > 0 && firstChildHeight > rootHeight) {
needsVerticalScrollBar = firstChildHeight - rootHeight > 1;
}
}
if (needsVerticalScrollBarState !== needsVerticalScrollBar) {
setNeedsVerticalScrollBar(needsVerticalScrollBar);
}
});
return function () { return async.dispose(); };
});
return needsVerticalScrollBarState;
}
function defaultFocusRestorer(options) {
var originalElement = options.originalElement, containsFocus = options.containsFocus;
if (originalElement && containsFocus && originalElement !== (0, Utilities_1.getWindow)()) {
// Make sure that the focus method actually exists
// In some cases the object might exist but not be a real element.
// This is primarily for IE 11 and should be removed once IE 11 is no longer in use.
// This is wrapped in a setTimeout because of a React 16 bug that is resolved in 17.
// Once we move to 17, the setTimeout should be removed (ref: https://github.com/facebook/react/issues/17894#issuecomment-656094405)
setTimeout(function () {
var _a;
(_a = originalElement.focus) === null || _a === void 0 ? void 0 : _a.call(originalElement);
}, 0);
}
}
function useRestoreFocus(props, root) {
var _a = props.onRestoreFocus, onRestoreFocus = _a === void 0 ? defaultFocusRestorer : _a;
var originalFocusedElement = React.useRef(undefined);
var containsFocus = React.useRef(false);
React.useEffect(function () {
originalFocusedElement.current = (0, Utilities_1.getDocument)().activeElement;
if ((0, Utilities_1.doesElementContainFocus)(root.current)) {
containsFocus.current = true;
}
return function () {
var _a;
onRestoreFocus === null || onRestoreFocus === void 0 ? void 0 : onRestoreFocus({
originalElement: originalFocusedElement.current,
containsFocus: containsFocus.current,
documentContainsFocus: ((_a = (0, Utilities_1.getDocument)()) === null || _a === void 0 ? void 0 : _a.hasFocus()) || false,
});
// De-reference DOM Node to avoid retainment via transpiled closure of _onKeyDown
originalFocusedElement.current = undefined;
};
// eslint-disable-next-line react-hooks/exhaustive-deps -- should only run on first render
}, []);
(0, react_hooks_1.useOnEvent)(root, 'focus', React.useCallback(function () {
containsFocus.current = true;
}, []), true);
(0, react_hooks_1.useOnEvent)(root, 'blur', React.useCallback(function (ev) {
/** The popup should update this._containsFocus when:
* relatedTarget exists AND
* the relatedTarget is not contained within the popup.
* If the relatedTarget is within the popup, that means the popup still has focus
* and focused moved from one element to another within the popup.
* If relatedTarget is undefined or null that usually means that a
* keyboard event occurred and focus didn't change
*/
if (root.current && ev.relatedTarget && !root.current.contains(ev.relatedTarget)) {
containsFocus.current = false;
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- should only run on first render
}, []), true);
}
function useHideSiblingNodes(props, root) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
var shouldHideSiblings = String(props['aria-modal']).toLowerCase() === 'true' && props.enableAriaHiddenSiblings;
React.useEffect(function () {
if (!(shouldHideSiblings && root.current)) {
return;
}
var unmodalize = (0, Utilities_1.modalize)(root.current);
return unmodalize;
}, [root, shouldHideSiblings]);
}
/**
* This adds accessibility to Dialog and Panel controls
*/
exports.Popup = React.forwardRef(function (propsWithoutDefaults, forwardedRef) {
var props = (0, Utilities_1.getPropsWithDefaults)({ shouldRestoreFocus: true, enableAriaHiddenSiblings: true }, propsWithoutDefaults);
var root = React.useRef(undefined);
var mergedRootRef = (0, react_hooks_1.useMergedRefs)(root, forwardedRef);
useHideSiblingNodes(props, root);
useRestoreFocus(props, root);
var role = props.role, className = props.className, ariaLabel = props.ariaLabel, ariaLabelledBy = props.ariaLabelledBy, ariaDescribedBy = props.ariaDescribedBy, style = props.style, children = props.children, onDismiss = props.onDismiss;
var needsVerticalScrollBar = useScrollbarAsync(props, root);
var onKeyDown = React.useCallback(function (ev) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
switch (ev.which) {
case Utilities_1.KeyCodes.escape:
if (onDismiss) {
onDismiss(ev);
ev.preventDefault();
ev.stopPropagation();
}
break;
}
}, [onDismiss]);
var win = (0, react_window_provider_1.useWindow)();
(0, react_hooks_1.useOnEvent)(win, 'keydown', onKeyDown);
return (React.createElement("div", tslib_1.__assign({ ref: mergedRootRef }, (0, Utilities_1.getNativeProps)(props, Utilities_1.divProperties), { className: className, role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, onKeyDown: onKeyDown, style: tslib_1.__assign({ overflowY: needsVerticalScrollBar ? 'scroll' : undefined, outline: 'none' }, style) }), children));
});
exports.Popup.displayName = 'Popup';
//# sourceMappingURL=Popup.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,62 @@
import * as React from 'react';
/**
* {@docCategory Popup}
*/
export interface IPopupProps extends React.HTMLAttributes<HTMLDivElement>, React.RefAttributes<HTMLDivElement> {
/**
* Aria role for popup
*/
role?: string;
/**
* Accessible label text for the popup.
*/
ariaLabel?: string;
/**
* Defines the element id referencing the element containing label text for popup.
*/
ariaLabelledBy?: string;
/**
* Defines the element id referencing the element containing the description for the popup.
*/
ariaDescribedBy?: string;
/**
* A callback function for when the popup is dismissed from the close button or light dismiss. If provided, will
* handle escape key press and call this. The event will be stopped/canceled.
*/
onDismiss?: (ev?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement> | KeyboardEvent) => any;
/**
* Optional class name for the root popup div.
*/
className?: string;
/**
* If true, when this component is unmounted, focus will be restored to the element that had focus when the component
* first mounted.
* @defaultvalue true
* @deprecated use restoreFocus callback instead
*/
shouldRestoreFocus?: boolean;
/**
* Called when the component is unmounting, and focus needs to be restored. If this is provided,
* focus will not be restored automatically, and you'll need to call `params.originalElement.focus()`.
*/
onRestoreFocus?: (params: IPopupRestoreFocusParams) => void;
/**
* Puts aria-hidden=true on all non-ancestors of the current popup, for screen readers.
* @defaultvalue true
* @deprecated Setting this to `false` is deprecated since it breaks modal behavior for some screen readers.
* It will not be supported in future versions of the library.
*/
enableAriaHiddenSiblings?: boolean;
}
/**
* Parameters passed to `onRestoreFocus` callback of `Popup` and related components.
* {@docCategory Popup}
*/
export interface IPopupRestoreFocusParams {
/** Element the underlying Popup believes focus should go to */
originalElement?: HTMLElement | Window | null;
/** Whether the popup currently contains focus */
containsFocus: boolean;
/** Whether the document the popup belongs to contains focus (or false if unknown) */
documentContainsFocus: boolean;
}
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=Popup.types.js.map
@@ -0,0 +1 @@
{"version":3,"file":"Popup.types.js","sourceRoot":"../src/","sources":["components/Popup/Popup.types.ts"],"names":[],"mappings":"","sourcesContent":["import * as React from 'react';\n\n/**\n * {@docCategory Popup}\n */\nexport interface IPopupProps extends React.HTMLAttributes<HTMLDivElement>, React.RefAttributes<HTMLDivElement> {\n /**\n * Aria role for popup\n */\n role?: string;\n\n /**\n * Accessible label text for the popup.\n */\n ariaLabel?: string;\n\n /**\n * Defines the element id referencing the element containing label text for popup.\n */\n ariaLabelledBy?: string;\n\n /**\n * Defines the element id referencing the element containing the description for the popup.\n */\n ariaDescribedBy?: string;\n\n /**\n * A callback function for when the popup is dismissed from the close button or light dismiss. If provided, will\n * handle escape key press and call this. The event will be stopped/canceled.\n */\n onDismiss?: (ev?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement> | KeyboardEvent) => any;\n\n /**\n * Optional class name for the root popup div.\n */\n className?: string;\n\n /**\n * If true, when this component is unmounted, focus will be restored to the element that had focus when the component\n * first mounted.\n * @defaultvalue true\n * @deprecated use restoreFocus callback instead\n */\n shouldRestoreFocus?: boolean;\n\n /**\n * Called when the component is unmounting, and focus needs to be restored. If this is provided,\n * focus will not be restored automatically, and you'll need to call `params.originalElement.focus()`.\n */\n onRestoreFocus?: (params: IPopupRestoreFocusParams) => void;\n\n /**\n * Puts aria-hidden=true on all non-ancestors of the current popup, for screen readers.\n * @defaultvalue true\n * @deprecated Setting this to `false` is deprecated since it breaks modal behavior for some screen readers.\n * It will not be supported in future versions of the library.\n */\n enableAriaHiddenSiblings?: boolean;\n}\n\n/**\n * Parameters passed to `onRestoreFocus` callback of `Popup` and related components.\n * {@docCategory Popup}\n */\nexport interface IPopupRestoreFocusParams {\n /** Element the underlying Popup believes focus should go to */\n originalElement?: HTMLElement | Window | null;\n /** Whether the popup currently contains focus */\n containsFocus: boolean;\n /** Whether the document the popup belongs to contains focus (or false if unknown) */\n documentContainsFocus: boolean;\n}\n"]}
@@ -0,0 +1,2 @@
export * from './Popup';
export * from './Popup.types';
+6
View File
@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
tslib_1.__exportStar(require("./Popup"), exports);
tslib_1.__exportStar(require("./Popup.types"), exports);
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"../src/","sources":["components/Popup/index.ts"],"names":[],"mappings":";;;AAAA,kDAAwB;AACxB,wDAA8B","sourcesContent":["export * from './Popup';\nexport * from './Popup.types';\n"]}