Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@
"rsp-rules/no-non-shadow-contains": ["error"],
"rsp-rules/safe-event-target": ["error"],
"rsp-rules/shadow-safe-active-element": ["error"],
"rsp-rules/shadow-safe-parent-element": ["error"],
"rsp-rules/shadow-safe-parent-node": ["error"],
"rsp-rules/faster-node-contains": ["error"],
"rsp-rules/imports": ["error"],
"rsp-rules/use-layout-effect-rule": ["error"],
Expand Down Expand Up @@ -277,6 +279,8 @@
"rsp-rules/no-non-shadow-contains": "off",
"rsp-rules/safe-event-target": "off",
"rsp-rules/shadow-safe-active-element": "off",
"rsp-rules/shadow-safe-parent-element": "off",
"rsp-rules/shadow-safe-parent-node": "off",
"rsp-rules/faster-node-contains": "off",
"rsp-rules/imports": "off",
"monorepo/no-internal-import": "off",
Expand Down Expand Up @@ -358,7 +362,9 @@
"rules": {
"rsp-rules/faster-node-contains": "off",
"rsp-rules/no-non-shadow-contains": "off",
"rsp-rules/shadow-safe-active-element": "off"
"rsp-rules/shadow-safe-active-element": "off",
"rsp-rules/shadow-safe-parent-element": "off",
"rsp-rules/shadow-safe-parent-node": "off"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {classNames} from '../utils/classNames';
import {ClearSlots, SlotProvider, useSlotProps} from '../utils/Slots';
import {filterDOMProps} from 'react-aria/filterDOMProps';
import {FocusScope} from 'react-aria/FocusScope';
import {getParentElement} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import intlMessages from '../../intl/actiongroup/*.json';
import {Item} from 'react-stately/Item';
import {ListState, useListState} from 'react-stately/useListState';
Expand Down Expand Up @@ -257,7 +258,7 @@ export const ActionGroup = forwardRef(function ActionGroup<T extends object>(
let parentRef = useMemo(
() => ({
get current() {
return wrapperRef.current?.parentElement;
return getParentElement(wrapperRef.current);
}
}),
// oxlint-disable-next-line react/react-compiler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import {Alignment, DOMProps, DOMRef, Orientation, StyleProps} from '@react-types/shared';
import {classNames} from '../utils/classNames';
import {filterDOMProps} from 'react-aria/filterDOMProps';
import {getParentElement} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import {Provider, useProvider, useProviderProps} from '../provider/Provider';
import React, {ReactNode, useCallback, useRef} from 'react';
import {SlotProvider, useSlotProps} from '../utils/Slots';
Expand Down Expand Up @@ -101,7 +102,7 @@ export const ButtonGroup = React.forwardRef(function ButtonGroup(
// oxlint-disable react/react-compiler, react-hooks/exhaustive-deps
useLayoutEffect(() => {
if (domRef.current) {
parent.current = domRef.current.parentElement as HTMLElement;
parent.current = getParentElement(domRef.current) as HTMLElement;
}
}, [domRef.current]);
// oxlint-enable react/react-compiler, react-hooks/exhaustive-deps
Expand Down
12 changes: 7 additions & 5 deletions packages/@adobe/react-spectrum/src/datepicker/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Alert from '@spectrum-icons/ui/AlertMedium';
import Checkmark from '@spectrum-icons/ui/CheckmarkMedium';
import {classNames} from '../utils/classNames';
import datepickerStyles from './styles.css';
import {getParentElement} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import {mergeProps} from 'react-aria/mergeProps';
import {mergeRefs} from 'react-aria/mergeRefs';
import React, {ReactElement, useCallback, useRef} from 'react';
Expand Down Expand Up @@ -45,26 +46,27 @@ export const Input = React.forwardRef(function Input(props: any, ref: any) {
let onResize = useCallback(
() =>
setReservePadding(function* (reservePadding) {
if (inputRef.current && inputRef.current.parentElement) {
let parentElement = getParentElement(inputRef.current) as HTMLElement | null;
if (inputRef.current && parentElement) {
if (reservePadding) {
// Try to collapse padding if the content is clipped.
if (inputRef.current.scrollWidth > inputRef.current.offsetWidth) {
let width = inputRef.current.parentElement.offsetWidth;
let width = parentElement.offsetWidth;
yield false;

// If removing padding causes a layout shift, add it back.
if (inputRef.current.parentElement.offsetWidth !== width) {
if (parentElement.offsetWidth !== width) {
yield true;
}
}
} else {
// Try to add padding if the content is not clipped.
if (inputRef.current.offsetWidth >= inputRef.current.scrollWidth) {
let width = inputRef.current.parentElement.offsetWidth;
let width = parentElement.offsetWidth;
yield true;

// If adding padding does not change the width (i.e. width is constrained), remove it again.
if (inputRef.current.parentElement.offsetWidth === width) {
if (parentElement.offsetWidth === width) {
yield false;
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/@adobe/react-spectrum/src/provider/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import clsx from 'clsx';
import {Context} from './context';
import {DOMRef} from '@react-types/shared';
import {filterDOMProps} from 'react-aria/filterDOMProps';
import {getParentElement} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import {getVersion} from './version-macro' with {type: 'macro'};
import {I18nProvider, useLocale} from 'react-aria/I18nProvider';
import {ModalProvider, useModalProvider} from 'react-aria/private/overlays/useModal';
Expand Down Expand Up @@ -181,7 +182,7 @@ const ProviderWrapper = React.forwardRef(function ProviderWrapper(
let hasWarned = useRef(false);
useEffect(() => {
if (direction && domRef.current) {
let closestDir = domRef.current?.parentElement?.closest('[dir]');
let closestDir = getParentElement(domRef.current)?.closest('[dir]');
let dir = closestDir && closestDir.getAttribute('dir');
if (dir && dir !== direction && !hasWarned.current && process.env.NODE_ENV !== 'production') {
console.warn(`Language directions cannot be nested. ${direction} inside ${dir}.`);
Expand Down
11 changes: 4 additions & 7 deletions packages/@adobe/react-spectrum/src/tag/TagGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import {Field} from '../label/Field';
import {FocusRing} from 'react-aria/FocusRing';
import {FocusScope} from 'react-aria/FocusScope';
import {getParentElement} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import intlMessages from '../../intl/tag/*.json';
import {ListCollection} from 'react-stately/private/list/ListCollection';
import {ListKeyboardDelegate} from 'react-aria/ListKeyboardDelegate';
Expand Down Expand Up @@ -177,19 +178,15 @@ export const TagGroup = React.forwardRef(function TagGroup<T extends object>(

// Remove tags until there is space for the collapse button and action button (if present) on the last row.
let buttons = [...currActionsRef.children];
if (
maxRows &&
buttons.length > 0 &&
rowCount >= maxRows &&
currContainerRef.parentElement
) {
let parentElement = getParentElement(currContainerRef);
if (maxRows && buttons.length > 0 && rowCount >= maxRows && parentElement) {
let buttonsWidth = buttons.reduce(
(acc, curr) => (acc += curr.getBoundingClientRect().width),
0
);
buttonsWidth += TAG_STYLES[scale].margin * 2 * buttons.length;
let end = direction === 'ltr' ? 'right' : 'left';
let containerEnd = currContainerRef.parentElement.getBoundingClientRect()[end];
let containerEnd = parentElement.getBoundingClientRect()[end];
let lastTagEnd = tags[index - 1]?.getBoundingClientRect()[end];
lastTagEnd += TAG_STYLES[scale].margin;
let availableWidth = containerEnd - lastTagEnd;
Expand Down
8 changes: 6 additions & 2 deletions packages/@react-spectrum/ai/src/PromptFieldContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
token
} from './tokens.macro' with {type: 'macro'};
import {color, css, style, StyleString} from '@react-spectrum/s2/style' with {type: 'macro'};
import {getEventTarget, nodeContains} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import {
getEventTarget,
getParentElement,
nodeContains
} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import {Group, GroupProps} from 'react-aria-components/Group';
import {isFocusable} from 'react-aria/private/utils/isFocusable';
import {keyframes} from './style/style-macro' with {type: 'macro'};
Expand Down Expand Up @@ -398,7 +402,7 @@ export function PromptFieldContainer(props: PropFieldContainerProps) {
// If not clicking on something focusable within the prompt field, focus the input.
let target = getEventTarget(e) as Element | null;
while (target && target !== e.currentTarget && !isFocusable(target)) {
target = target.parentElement;
target = getParentElement(target);
}

if (target === e.currentTarget) {
Expand Down
3 changes: 2 additions & 1 deletion packages/@react-spectrum/s2/src/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
} from 'react';
import FolderIcon from '../s2wf-icons/S2_Icon_FolderBreadcrumb_20_N.svg';
import {forwardRefType} from './types';
import {getParentElement} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import {HeadingContext} from 'react-aria-components/Heading';
import {inertValue} from 'react-aria/private/utils/inertValue';
import intlMessages from '../intl/*.json';
Expand Down Expand Up @@ -467,7 +468,7 @@ let useCollectionRender = (collection: Collection<Node<unknown>>) => {
setVisibleItems(collection.size);
return;
}
let container = currListRef.parentElement;
let container = getParentElement(currListRef) as HTMLElement | null;
if (!container) {
setVisibleItems(collection.size);
return;
Expand Down
3 changes: 2 additions & 1 deletion packages/@react-spectrum/s2/src/ButtonGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {createContext, forwardRef, ReactNode, useCallback, useRef} from 'react';
import {DOMProps, DOMRef, DOMRefValue} from '@react-types/shared';
import {filterDOMProps} from 'react-aria/filterDOMProps';
import {getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {getParentElement} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import {style} from '../style' with {type: 'macro'};
import {useDOMRef} from './useDOMRef';
import {useLayoutEffect} from 'react-aria/private/utils/useLayoutEffect';
Expand Down Expand Up @@ -168,7 +169,7 @@ export const ButtonGroup = forwardRef(function ButtonGroup(
// oxlint-disable react/react-compiler, react-hooks/exhaustive-deps
useLayoutEffect(() => {
if (domRef.current) {
parent.current = domRef.current.parentElement as HTMLElement;
parent.current = getParentElement(domRef.current) as HTMLElement;
}
}, [domRef.current]);
// oxlint-enable react/react-compiler, react-hooks/exhaustive-deps
Expand Down
3 changes: 2 additions & 1 deletion packages/@react-spectrum/s2/src/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import {DragPreview} from './DragPreview';
import {Form} from 'react-aria-components/Form';
import {
getActiveElement,
getParentElement,
isFocusWithin,
nodeContains
} from 'react-aria/private/utils/shadowdom/DOMFunctions';
Expand Down Expand Up @@ -1690,7 +1691,7 @@ function EditableCellInner(
}
let width = cellRef.current?.clientWidth || 0;
let cell = cellRef.current;
let boundingRect = cell?.parentElement?.getBoundingClientRect();
let boundingRect = getParentElement(cell)?.getBoundingClientRect();
let verticalOffset = (boundingRect?.top ?? 0) - (boundingRect?.bottom ?? 0);

let tableWidth = cellRef.current?.closest('[role="grid"],[role="treegrid"]')?.clientWidth || 0;
Expand Down
6 changes: 4 additions & 2 deletions packages/@react-spectrum/s2/src/TagGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {FieldLabel, helpTextStyles} from './Field';
import {flushSync} from 'react-dom';
import {FormContext, useFormProps} from './Form';
import {forwardRefType} from './types';
import {getParentElement} from 'react-aria/private/utils/shadowdom/DOMFunctions';
import {IconContext} from './Icon';
import {ImageContext} from './Image';
import {inertValue} from 'react-aria/private/utils/inertValue';
Expand Down Expand Up @@ -214,7 +215,7 @@ function TagGroupInner<T>({
!currContainerRef ||
!currTagsRef ||
collection.size === 0 ||
currContainerRef.parentElement == null
getParentElement(currContainerRef) == null
) {
return {
visibleTagCount: 0,
Expand Down Expand Up @@ -254,7 +255,8 @@ function TagGroupInner<T>({
let margins = parseFloat(getComputedStyle(buttons[0]).marginInlineStart);
buttonsWidth += margins * 2;
let end = direction === 'ltr' ? 'right' : 'left';
let containerEnd = currContainerRef.parentElement?.getBoundingClientRect()[end] - margins;
let containerEnd =
getParentElement(currContainerRef)?.getBoundingClientRect()[end] - margins;
let lastTagEnd = tags[index - 1]?.getBoundingClientRect()[end];
lastTagEnd += margins;
let availableWidth = containerEnd - lastTagEnd;
Expand Down
4 changes: 4 additions & 0 deletions packages/dev/eslint-plugin-rsp-rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import noReactKey from './rules/no-react-key.js';
import pureRender from './rules/pure-render.js';
import safeEventTarget from './rules/safe-event-target.js';
import shadowSafeActiveElement from './rules/shadow-safe-active-element.js';
import shadowSafeParentElement from './rules/shadow-safe-parent-element.js';
import shadowSafeParentNode from './rules/shadow-safe-parent-node.js';
import sortImports from './rules/sort-imports.js';
import useLayoutEffectRule from './rules/use-layout-effect-rule.js';

Expand All @@ -36,6 +38,8 @@ const rules = {
'no-non-shadow-contains': noNonShadowContains,
'safe-event-target': safeEventTarget,
'shadow-safe-active-element': shadowSafeActiveElement,
'shadow-safe-parent-element': shadowSafeParentElement,
'shadow-safe-parent-node': shadowSafeParentNode,
'faster-node-contains': fasterNodeContains,
imports,
'use-layout-effect-rule': useLayoutEffectRule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* governing permissions and limitations under the License.
*/

const DOM_FUNCTIONS = 'react-aria/private/utils/shadowdom/DOMFunctions';

const plugin = {
meta: {
type: 'suggestion',
Expand All @@ -27,17 +29,19 @@ const plugin = {
create: context => {
let hasGetActiveElementImport = false;
let getActiveElementLocalName = 'getActiveElement';
let existingReactAriaUtilsImport = null;
let existingDOMFunctionsImport = null;

return {
// Track imports from @react-aria/utils
// Track imports from DOMFunctions, and from the legacy @react-aria/utils package
ImportDeclaration(node) {
if (
node.source &&
node.source.type === 'Literal' &&
node.source.value === '@react-aria/utils'
(node.source.value === DOM_FUNCTIONS || node.source.value === '@react-aria/utils')
) {
existingReactAriaUtilsImport = node;
if (node.source.value === DOM_FUNCTIONS) {
existingDOMFunctionsImport = node;
}
// Check if getActiveElement is already imported
const hasGetActiveElement = node.specifiers.some(
spec =>
Expand Down Expand Up @@ -72,32 +76,32 @@ const plugin = {

// Add import if not present
if (!hasGetActiveElementImport) {
if (existingReactAriaUtilsImport) {
// Add getActiveElement to existing @react-aria/utils import
const specifiers = existingReactAriaUtilsImport.specifiers;
if (existingDOMFunctionsImport) {
// Add getActiveElement to existing DOMFunctions import
const specifiers = existingDOMFunctionsImport.specifiers;
if (specifiers.length > 0) {
fixes.push(
fixer.insertTextAfter(
sourceCode.getFirstToken(
existingReactAriaUtilsImport,
existingDOMFunctionsImport,
token => token.value === '{'
),
'getActiveElement, '
)
);
}
} else {
// No existing import from @react-aria/utils, create a new one
// No existing import from DOMFunctions, create a new one
const programNode = context.sourceCode.ast;
const imports = programNode.body.filter(node => node.type === 'ImportDeclaration');

if (imports.length > 0) {
const lastImport = imports[imports.length - 1];
const importStatement = "\nimport {getActiveElement} from '@react-aria/utils';";
const importStatement = `\nimport {getActiveElement} from '${DOM_FUNCTIONS}';`;
fixes.push(fixer.insertTextAfter(lastImport, importStatement));
} else {
// No imports, add at the beginning
const importStatement = "import {getActiveElement} from '@react-aria/utils';\n";
const importStatement = `import {getActiveElement} from '${DOM_FUNCTIONS}';\n`;
fixes.push(fixer.insertTextBefore(programNode.body[0], importStatement));
}
}
Expand Down
Loading
Loading