=
supportedProps.ref = setRef;
- return createElement(component, supportedProps);
+ return createElement(component, supportedProps, rootContext.styleResolver);
}
);
diff --git a/packages/react-native-web/src/exports/createElement/__tests__/index-test.js b/packages/react-native-web/src/exports/createElement/__tests__/index-test.js
index 6401a43626..2b3f9f9ae9 100644
--- a/packages/react-native-web/src/exports/createElement/__tests__/index-test.js
+++ b/packages/react-native-web/src/exports/createElement/__tests__/index-test.js
@@ -3,6 +3,7 @@
import createElement from '..';
import React from 'react';
import { render } from '@testing-library/react';
+import StyleResolver from '../../StyleSheet/StyleResolver';
function getAttribute(container, attribute) {
return container.firstChild.getAttribute(attribute);
@@ -12,27 +13,45 @@ function getProperty(container, prop) {
return container.firstChild[prop];
}
+let styleResolver;
+
describe('exports/createElement', () => {
+ beforeEach(() => {
+ styleResolver = new StyleResolver();
+ });
+
+ afterEach(() => {
+ styleResolver.clear();
+ });
+
test('renders different DOM elements', () => {
- let { container } = render(createElement('span'));
+ let { container } = render(createElement('span', {}, styleResolver));
expect(container.firstChild).toMatchSnapshot();
- ({ container } = render(createElement('main')));
+ ({ container } = render(createElement('main', {}, styleResolver)));
expect(container.firstChild).toMatchSnapshot();
({ container } = render(
- createElement('svg', { children: createElement('image', { href: '#href' }) })
+ createElement(
+ 'svg',
+ { children: createElement('image', { href: '#href' }, styleResolver) },
+ styleResolver
+ )
));
expect(container.firstChild).toMatchSnapshot();
});
describe('prop "accessibilityRole"', () => {
test('string component type', () => {
- const { container } = render(createElement('span', { accessibilityRole: 'link' }));
+ const { container } = render(
+ createElement('span', { accessibilityRole: 'link' }, styleResolver)
+ );
expect(container.firstChild.nodeName).toBe('SPAN');
});
test('function component type', () => {
const Custom = () => ;
- const { container } = render(createElement(Custom, { accessibilityRole: 'link' }));
+ const { container } = render(
+ createElement(Custom, { accessibilityRole: 'link' }, styleResolver)
+ );
expect(container.firstChild.nodeName).toBe('DIV');
});
});
@@ -40,126 +59,152 @@ describe('exports/createElement', () => {
describe('accessibility props', () => {
test('accessibilityActiveDescendant', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityActiveDescendant: null })
+ createElement('div', { accessibilityActiveDescendant: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-activedescendant')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityActiveDescendant: 'abc' })
+ createElement('div', { accessibilityActiveDescendant: 'abc' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-activedescendant')).toBe('abc');
});
test('accessibilityAtomic', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityAtomic: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityAtomic: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-atomic')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityAtomic: true }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityAtomic: true }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-atomic')).toBe('true');
});
test('accessibilityAutoComplete', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityAutoComplete: null })
+ createElement('div', { accessibilityAutoComplete: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-autocomplete')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityAutoComplete: true })
+ createElement('div', { accessibilityAutoComplete: true }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-autocomplete')).toBe('true');
});
test('accessibilityBusy', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityBusy: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityBusy: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-busy')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityBusy: true }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityBusy: true }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-busy')).toBe('true');
});
test('accessibilityChecked', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityChecked: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityChecked: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-checked')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityChecked: true }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityChecked: true }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-checked')).toBe('true');
});
test('accessibilityColumnCount', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityColumnCount: null })
+ createElement('div', { accessibilityColumnCount: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-colcount')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityColumnCount: 5 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityColumnCount: 5 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-colcount')).toBe('5');
});
test('accessibilityColumnIndex', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityColumnIndex: null })
+ createElement('div', { accessibilityColumnIndex: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-colindex')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityColumnIndex: 5 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityColumnIndex: 5 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-colindex')).toBe('5');
});
test('accessibilityColumnSpan', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityColumnSpan: null })
+ createElement('div', { accessibilityColumnSpan: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-colspan')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityColumnSpan: 5 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityColumnSpan: 5 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-colspan')).toBe('5');
});
test('accessibilityControls', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityControls: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityControls: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-controls')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityControls: 'abc' })
+ createElement('div', { accessibilityControls: 'abc' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-controls')).toBe('abc');
const { container: hasMultipleValues } = render(
- createElement('div', { accessibilityControls: ['abc', 'def'] })
+ createElement('div', { accessibilityControls: ['abc', 'def'] }, styleResolver)
);
expect(getAttribute(hasMultipleValues, 'aria-controls')).toBe('abc def');
});
test('accessibilityCurrent', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityCurrent: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityCurrent: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-current')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityCurrent: 'page' })
+ createElement('div', { accessibilityCurrent: 'page' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-current')).toBe('page');
});
test('accessibilityDescribedBy', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityDescribedBy: null })
+ createElement('div', { accessibilityDescribedBy: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-describedby')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityDescribedBy: 'abc' })
+ createElement('div', { accessibilityDescribedBy: 'abc' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-describedby')).toBe('abc');
const { container: hasMultipleValues } = render(
- createElement('div', { accessibilityDescribedBy: ['abc', 'def'] })
+ createElement('div', { accessibilityDescribedBy: ['abc', 'def'] }, styleResolver)
);
expect(getAttribute(hasMultipleValues, 'aria-describedby')).toBe('abc def');
});
test('accessibilityDetails', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityDetails: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityDetails: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-details')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityDetails: 'abc' }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityDetails: 'abc' }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-details')).toBe('abc');
});
test('accessibilityDisabled', () => {
const { container: isEmpty } = render(
- createElement('button', { accessibilityDisabled: null })
+ createElement('button', { accessibilityDisabled: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-disabled')).toBeNull();
expect(getProperty(isEmpty, 'disabled')).toBe(false);
const { container: hasValue } = render(
- createElement('button', { accessibilityDisabled: true })
+ createElement('button', { accessibilityDisabled: true }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-disabled')).toBe('true');
expect(getProperty(hasValue, 'disabled')).toBe(true);
@@ -167,191 +212,239 @@ describe('exports/createElement', () => {
test('accessibilityErrorMessage', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityErrorMessage: null })
+ createElement('div', { accessibilityErrorMessage: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-errormessage')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityErrorMessage: 'abc' })
+ createElement('div', { accessibilityErrorMessage: 'abc' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-errormessage')).toBe('abc');
});
test('accessibilityExpanded', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityExpanded: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityExpanded: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-expanded')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityExpanded: true }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityExpanded: true }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-expanded')).toBe('true');
});
test('accessibilityFlowTo', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityFlowTo: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityFlowTo: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-flowto')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityFlowTo: 'abc' }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityFlowTo: 'abc' }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-flowto')).toBe('abc');
const { container: hasMultipleValues } = render(
- createElement('div', { accessibilityFlowTo: ['abc', 'def'] })
+ createElement('div', { accessibilityFlowTo: ['abc', 'def'] }, styleResolver)
);
expect(getAttribute(hasMultipleValues, 'aria-flowto')).toBe('abc def');
});
test('accessibilityHasPopup', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityHasPopup: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityHasPopup: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-haspopup')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityHasPopup: true }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityHasPopup: true }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-haspopup')).toBe('true');
});
test('accessibilityHidden', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityHidden: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityHidden: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-hidden')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityHidden: true }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityHidden: true }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-hidden')).toBe('true');
});
test('accessibilityInvalid', () => {
- const { container: isEmpty } = render(createElement('input', { accessibilityInvalid: null }));
+ const { container: isEmpty } = render(
+ createElement('input', { accessibilityInvalid: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-invalid')).toBeNull();
const { container: hasValue } = render(
- createElement('input', { accessibilityInvalid: true })
+ createElement('input', { accessibilityInvalid: true }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-invalid')).toBe('true');
});
test('accessibilityKeyShortcuts', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityKeyShortcuts: null })
+ createElement('div', { accessibilityKeyShortcuts: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-keyshortcuts')).toBeNull();
const { container: hasValue } = render(
- createElement('div', {
- accessibilityKeyShortcuts: ['ArrowUp', 'Enter', 'Space', 'Alt+Shift+T']
- })
+ createElement(
+ 'div',
+ {
+ accessibilityKeyShortcuts: ['ArrowUp', 'Enter', 'Space', 'Alt+Shift+T']
+ },
+ styleResolver
+ )
);
expect(getAttribute(hasValue, 'aria-keyshortcuts')).toBe('ArrowUp Enter Space Alt+Shift+T');
});
test('accessibilityLabel', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityLabel: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityLabel: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-label')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityLabel: 'abc' }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityLabel: 'abc' }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-label')).toBe('abc');
});
test('accessibilityLabelledBy', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityLabelledBy: null })
+ createElement('div', { accessibilityLabelledBy: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-labelledby')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityLabelledBy: 'abc' })
+ createElement('div', { accessibilityLabelledBy: 'abc' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-labelledby')).toBe('abc');
const { container: hasMultipleValues } = render(
- createElement('div', { accessibilityLabelledBy: ['abc', 'def'] })
+ createElement('div', { accessibilityLabelledBy: ['abc', 'def'] }, styleResolver)
);
expect(getAttribute(hasMultipleValues, 'aria-labelledby')).toBe('abc def');
});
test('accessibilityLevel', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityLevel: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityLevel: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-level')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityLevel: 3 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityLevel: 3 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-level')).toBe('3');
});
test('accessibilityLiveRegion', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityLiveRegion: null })
+ createElement('div', { accessibilityLiveRegion: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-live')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityLiveRegion: 'polite' })
+ createElement('div', { accessibilityLiveRegion: 'polite' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-live')).toBe('polite');
});
test('accessibilityModal', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityModal: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityModal: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-modal')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityModal: true }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityModal: true }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-modal')).toBe('true');
});
test('accessibilityMultiline', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityMultiline: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityMultiline: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-multiline')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityMultiline: true })
+ createElement('div', { accessibilityMultiline: true }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-multiline')).toBe('true');
});
test('accessibilityMultiSelectable', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityMultiSelectable: null })
+ createElement('div', { accessibilityMultiSelectable: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-multiselectable')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityMultiSelectable: true })
+ createElement('div', { accessibilityMultiSelectable: true }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-multiselectable')).toBe('true');
});
test('accessibilityOrientation', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityOrientation: null })
+ createElement('div', { accessibilityOrientation: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-orientation')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityOrientation: 'vertical' })
+ createElement('div', { accessibilityOrientation: 'vertical' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-orientation')).toBe('vertical');
});
test('accessibilityOwns', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityOwns: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityOwns: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-owns')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityOwns: 'abc' }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityOwns: 'abc' }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-owns')).toBe('abc');
const { container: hasMultipleValues } = render(
- createElement('div', { accessibilityOwns: ['abc', 'def'] })
+ createElement('div', { accessibilityOwns: ['abc', 'def'] }, styleResolver)
);
expect(getAttribute(hasMultipleValues, 'aria-owns')).toBe('abc def');
});
test('accessibilityPlaceholder', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityPlaceholder: null })
+ createElement('div', { accessibilityPlaceholder: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-placeholder')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityPlaceholder: 'MM-DD-YYYY' })
+ createElement('div', { accessibilityPlaceholder: 'MM-DD-YYYY' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-placeholder')).toBe('MM-DD-YYYY');
});
test('accessibilityPosInSet', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityPosInSet: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityPosInSet: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-posinset')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityPosInSet: 3 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityPosInSet: 3 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-posinset')).toBe('3');
});
test('accessibilityPressed', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityPressed: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityPressed: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-pressed')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityPressed: true }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityPressed: true }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-pressed')).toBe('true');
});
test('accessibilityReadOnly', () => {
const { container: isEmpty } = render(
- createElement('input', { accessibilityReadOnly: null })
+ createElement('input', { accessibilityReadOnly: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-readonly')).toBeNull();
expect(getProperty(isEmpty, 'readOnly')).toBe(false);
const { container: hasValue } = render(
- createElement('input', { accessibilityReadOnly: true })
+ createElement('input', { accessibilityReadOnly: true }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-readonly')).toBe('true');
expect(getProperty(hasValue, 'readOnly')).toBe(true);
@@ -359,122 +452,168 @@ describe('exports/createElement', () => {
test('accessibilityRequired', () => {
const { container: isEmpty } = render(
- createElement('input', { accessibilityRequired: null })
+ createElement('input', { accessibilityRequired: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-required')).toBeNull();
expect(getProperty(isEmpty, 'required')).toBe(false);
const { container: hasValue } = render(
- createElement('input', { accessibilityRequired: true })
+ createElement('input', { accessibilityRequired: true }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-required')).toBe('true');
expect(getProperty(hasValue, 'required')).toBe(true);
});
test('accessibilityRole', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityRole: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityRole: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'role')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityRole: 'button' }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityRole: 'button' }, styleResolver)
+ );
expect(getAttribute(hasValue, 'role')).toBe('button');
expect(getAttribute(hasValue, 'tabIndex')).toBe('0');
- const { container: roleIsNone } = render(createElement('div', { accessibilityRole: 'none' }));
+ const { container: roleIsNone } = render(
+ createElement('div', { accessibilityRole: 'none' }, styleResolver)
+ );
expect(getAttribute(roleIsNone, 'role')).toBe('presentation');
});
test('accessibilityRoleDescription', () => {
const { container: isEmpty } = render(
- createElement('div', { accessibilityRoleDescription: null })
+ createElement('div', { accessibilityRoleDescription: null }, styleResolver)
);
expect(getAttribute(isEmpty, 'aria-roledescription')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityRoleDescription: 'abc' })
+ createElement('div', { accessibilityRoleDescription: 'abc' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-roledescription')).toBe('abc');
});
test('accessibilityRowCount', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityRowCount: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityRowCount: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-rowcount')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityRowCount: 5 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityRowCount: 5 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-rowcount')).toBe('5');
});
test('accessibilityRowIndex', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityRowIndex: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityRowIndex: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-rowindex')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityRowIndex: 5 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityRowIndex: 5 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-rowindex')).toBe('5');
});
test('accessibilityRowSpan', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityRowSpan: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityRowSpan: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-rowspan')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityRowSpan: 5 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityRowSpan: 5 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-rowspan')).toBe('5');
});
test('accessibilitySelected', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilitySelected: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilitySelected: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-selected')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilitySelected: true }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilitySelected: true }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-selected')).toBe('true');
});
test('accessibilitySetSize', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilitySetSize: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilitySetSize: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-setsize')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilitySetSize: 5 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilitySetSize: 5 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-setsize')).toBe('5');
});
test('accessibilitySort', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilitySort: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilitySort: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-sort')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilitySort: 'ascending' })
+ createElement('div', { accessibilitySort: 'ascending' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-sort')).toBe('ascending');
});
test('accessibilityValueMax', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityValueMax: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityValueMax: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-valuemax')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityValueMax: 100 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityValueMax: 100 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-valuemax')).toBe('100');
});
test('accessibilityValueMin', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityValueMin: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityValueMin: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-valuemin')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityValueMin: 10 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityValueMin: 10 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-valuemin')).toBe('10');
});
test('accessibilityValueNow', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityValueNow: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityValueNow: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-valuenow')).toBeNull();
- const { container: hasValue } = render(createElement('div', { accessibilityValueNow: 50 }));
+ const { container: hasValue } = render(
+ createElement('div', { accessibilityValueNow: 50 }, styleResolver)
+ );
expect(getAttribute(hasValue, 'aria-valuenow')).toBe('50');
});
test('accessibilityValueText', () => {
- const { container: isEmpty } = render(createElement('div', { accessibilityValueText: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { accessibilityValueText: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'aria-valuetext')).toBeNull();
const { container: hasValue } = render(
- createElement('div', { accessibilityValueText: 'fifty' })
+ createElement('div', { accessibilityValueText: 'fifty' }, styleResolver)
);
expect(getAttribute(hasValue, 'aria-valuetext')).toBe('fifty');
});
test('dataSet', () => {
const { container: hasValue } = render(
- createElement('div', {
- dataSet: {
- one: '1',
- two: '2',
- camelCase: 'camelCase',
- msPrefix: 'msPrefix'
- }
- })
+ createElement(
+ 'div',
+ {
+ dataSet: {
+ one: '1',
+ two: '2',
+ camelCase: 'camelCase',
+ msPrefix: 'msPrefix'
+ }
+ },
+ styleResolver
+ )
);
expect(hasValue.firstChild).toMatchInlineSnapshot(`
{
});
test('focusable', () => {
- const { container: isEmpty } = render(createElement('div', { focusable: null }));
+ const { container: isEmpty } = render(
+ createElement('div', { focusable: null }, styleResolver)
+ );
expect(getAttribute(isEmpty, 'tabindex')).toBeNull();
- const { container: isTrue } = render(createElement('div', { focusable: true }));
+ const { container: isTrue } = render(
+ createElement('div', { focusable: true }, styleResolver)
+ );
expect(getAttribute(isTrue, 'tabindex')).toBe('0');
const { container: isFalseNativelyFocusable } = render(
- createElement('button', { focusable: false })
+ createElement('button', { focusable: false }, styleResolver)
);
expect(getAttribute(isFalseNativelyFocusable, 'tabindex')).toBe('-1');
const { container: isDisabledNativelyFocusable } = render(
- createElement('button', {
- accessibilityDisabled: true,
- focusable: true
- })
+ createElement(
+ 'button',
+ {
+ accessibilityDisabled: true,
+ focusable: true
+ },
+ styleResolver
+ )
);
expect(getAttribute(isDisabledNativelyFocusable, 'tabindex')).toBe('-1');
const { container: isTrueNativelyFocusable } = render(
- createElement('button', { focusable: true })
+ createElement('button', { focusable: true }, styleResolver)
);
expect(getAttribute(isTrueNativelyFocusable, 'tabindex')).toBeNull();
const { container: isFocusableRole } = render(
- createElement('div', { accessibilityRole: 'button', focusable: true })
+ createElement('div', { accessibilityRole: 'button', focusable: true }, styleResolver)
);
expect(getAttribute(isFocusableRole, 'tabindex')).toBe('0');
const { container: isFalseFocusableRole } = render(
- createElement('div', { accessibilityRole: 'button', focusable: false })
+ createElement('div', { accessibilityRole: 'button', focusable: false }, styleResolver)
);
expect(getAttribute(isFalseFocusableRole, 'tabindex')).toBe('-1');
});
diff --git a/packages/react-native-web/src/exports/createElement/index.js b/packages/react-native-web/src/exports/createElement/index.js
index 95e4b28283..fa0b98be9c 100644
--- a/packages/react-native-web/src/exports/createElement/index.js
+++ b/packages/react-native-web/src/exports/createElement/index.js
@@ -11,14 +11,14 @@ import AccessibilityUtil from '../../modules/AccessibilityUtil';
import createDOMProps from '../../modules/createDOMProps';
import React from 'react';
-const createElement = (component, props, ...children) => {
+const createElement = (component, props, styleResolver, ...children) => {
// Use equivalent platform elements where possible.
let accessibilityComponent;
if (component && component.constructor === String) {
accessibilityComponent = AccessibilityUtil.propsToAccessibilityComponent(props);
}
const Component = accessibilityComponent || component;
- const domProps = createDOMProps(Component, props);
+ const domProps = createDOMProps(Component, styleResolver, props);
return React.createElement(Component, domProps, ...children);
};
diff --git a/packages/react-native-web/src/index.js b/packages/react-native-web/src/index.js
index c7d8aacb1e..30e1a80df5 100644
--- a/packages/react-native-web/src/index.js
+++ b/packages/react-native-web/src/index.js
@@ -61,6 +61,7 @@ export { default as View } from './exports/View';
export { default as VirtualizedList } from './exports/VirtualizedList';
export { default as YellowBox } from './exports/YellowBox';
export { default as LogBox } from './exports/LogBox';
+export { default as ReactRootView } from './exports/AppRegistry/ReactRootView';
// compat (components)
export { default as DrawerLayoutAndroid } from './exports/DrawerLayoutAndroid';
diff --git a/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js b/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js
index 0a208eda99..990a7d06aa 100644
--- a/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js
+++ b/packages/react-native-web/src/modules/createDOMProps/__tests__/index-test.js
@@ -1,44 +1,47 @@
/* eslint-env jasmine, jest */
import createDOMProps from '..';
+import StyleResolver from '../../../exports/StyleSheet/StyleResolver';
-const createProps = (props) => createDOMProps(null, props);
+const createProps = (props, resolver) => createDOMProps(null, resolver, props);
describe('modules/createDOMProps', () => {
+ const resolver = new StyleResolver();
+
describe('focus-related accessibility attributes', () => {
test('with no accessibility props', () => {
- expect(createProps({})).toEqual({});
+ expect(createProps({}, resolver)).toEqual({});
});
describe('"accessibilityRole" of "link"', () => {
const accessibilityRole = 'link';
test('default case', () => {
- expect(createProps({ accessibilityRole })).toEqual(
+ expect(createProps({ accessibilityRole }, resolver)).toEqual(
expect.not.objectContaining({ tabIndex: '-1' })
);
});
test('when "focusable" is true', () => {
- expect(createProps({ accessibilityRole, focusable: true })).toEqual(
+ expect(createProps({ accessibilityRole, focusable: true }, resolver)).toEqual(
expect.not.objectContaining({ tabIndex: '-1' })
);
});
test('when "focusable" is false', () => {
- expect(createProps({ accessibilityRole, focusable: false })).toEqual(
+ expect(createProps({ accessibilityRole, focusable: false }, resolver)).toEqual(
expect.objectContaining({ tabIndex: '-1' })
);
});
test('when "accessibilityDisabled" is true', () => {
- expect(createProps({ accessibilityRole, accessibilityDisabled: true })).toEqual(
+ expect(createProps({ accessibilityRole, accessibilityDisabled: true }, resolver)).toEqual(
expect.objectContaining({ 'aria-disabled': true })
);
});
test('when "disabled" is false', () => {
- expect(createProps({ accessibilityRole, accessibilityDisabled: false })).toEqual(
+ expect(createProps({ accessibilityRole, accessibilityDisabled: false }, resolver)).toEqual(
expect.not.objectContaining({ tabIndex: '-1' })
);
});
@@ -46,31 +49,31 @@ describe('modules/createDOMProps', () => {
const testFocusableRole = (accessibilityRole) => {
test('default case', () => {
- expect(createProps({ accessibilityRole })).toEqual(
+ expect(createProps({ accessibilityRole }, resolver)).toEqual(
expect.objectContaining({ tabIndex: '0' })
);
});
test('when "focusable" is true', () => {
- expect(createProps({ accessibilityRole, focusable: true })).toEqual(
+ expect(createProps({ accessibilityRole, focusable: true }, resolver)).toEqual(
expect.objectContaining({ tabIndex: '0' })
);
});
test('when "focusable" is false', () => {
- expect(createProps({ accessibilityRole, focusable: false })).toEqual(
+ expect(createProps({ accessibilityRole, focusable: false }, resolver)).toEqual(
expect.objectContaining({ tabIndex: '-1' })
);
});
test('when "accessibilityDisabled" is true', () => {
- expect(createProps({ accessibilityRole, accessibilityDisabled: true })).toEqual(
+ expect(createProps({ accessibilityRole, accessibilityDisabled: true }, resolver)).toEqual(
expect.objectContaining({ 'aria-disabled': true })
);
});
test('when "accessibilityDisabled" is false', () => {
- expect(createProps({ accessibilityRole, accessibilityDisabled: false })).toEqual(
+ expect(createProps({ accessibilityRole, accessibilityDisabled: false }, resolver)).toEqual(
expect.objectContaining({ tabIndex: '0' })
);
});
@@ -82,13 +85,13 @@ describe('modules/createDOMProps', () => {
describe('with unfocusable accessibilityRole', () => {
test('when "focusable" is true', () => {
- expect(createProps({ focusable: true })).toEqual(
+ expect(createProps({ focusable: true }, resolver)).toEqual(
expect.objectContaining({ tabIndex: '0' })
);
});
test('when "focusable" is false', () => {
- expect(createProps({ focusable: false })).toEqual(
+ expect(createProps({ focusable: false }, resolver)).toEqual(
expect.objectContaining({ tabIndex: '-1' })
);
});
@@ -97,19 +100,19 @@ describe('modules/createDOMProps', () => {
test('prop "accessibilityLabel" becomes "aria-label"', () => {
const accessibilityLabel = 'accessibilityLabel';
- const props = createProps({ accessibilityLabel });
+ const props = createProps({ accessibilityLabel }, resolver);
expect(props['aria-label']).toEqual(accessibilityLabel);
});
test('prop "accessibilityLiveRegion" becomes "aria-live"', () => {
const accessibilityLiveRegion = 'none';
- const props = createProps({ accessibilityLiveRegion });
+ const props = createProps({ accessibilityLiveRegion }, resolver);
expect(props['aria-live']).toEqual('off');
});
test('prop "accessibilityRole" becomes "role"', () => {
const accessibilityRole = 'button';
- const props = createProps({ accessibilityRole });
+ const props = createProps({ accessibilityRole }, resolver);
expect(props.role).toEqual('button');
});
@@ -133,50 +136,54 @@ describe('modules/createDOMProps', () => {
test('values are "undefined"', () => {
const accessibilityState = createAccessibilityState(undefined);
- const props = createProps({ accessibilityState });
+ const props = createProps({ accessibilityState }, resolver);
expect(props).toMatchSnapshot();
});
test('values are "false"', () => {
const accessibilityState = createAccessibilityState(false);
- const props = createProps({ accessibilityState });
+ const props = createProps({ accessibilityState }, resolver);
expect(props).toMatchSnapshot();
});
test('values are "true"', () => {
const accessibilityState = createAccessibilityState(true);
- const props = createProps({ accessibilityState });
+ const props = createProps({ accessibilityState }, resolver);
expect(props).toMatchSnapshot();
});
});
test('prop "className" is preserved', () => {
const className = 'external-class-name';
- const props = createProps({ className });
+ const props = createProps({ className }, resolver);
expect(props.className).toEqual(className);
});
test('prop "nativeID" becomes "id"', () => {
const nativeID = 'Example.nativeID';
- const props = createProps({ nativeID });
+ const props = createProps({ nativeID }, resolver);
expect(props.id).toEqual(nativeID);
});
test('prop "testID" becomes "data-testid"', () => {
const testID = 'Example.testID';
- const props = createProps({ testID });
+ const props = createProps({ testID }, resolver);
expect(props['data-testid']).toEqual(testID);
});
test('includes cursor style for pressable roles', () => {
- expect(createDOMProps('span', { accessibilityRole: 'link' }).className).toMatchSnapshot();
- expect(createDOMProps('span', { accessibilityRole: 'button' }).className).toMatchSnapshot();
+ expect(
+ createDOMProps('span', resolver, { accessibilityRole: 'link' }).className
+ ).toMatchSnapshot();
+ expect(
+ createDOMProps('span', resolver, { accessibilityRole: 'button' }).className
+ ).toMatchSnapshot();
});
test('includes base reset style for browser-styled elements', () => {
- expect(createDOMProps('a').className).toMatchSnapshot();
- expect(createDOMProps('button').className).toMatchSnapshot();
- expect(createDOMProps('li').className).toMatchSnapshot();
- expect(createDOMProps('ul').className).toMatchSnapshot();
+ expect(createDOMProps('a', resolver, {}).className).toMatchSnapshot();
+ expect(createDOMProps('button', resolver, {}).className).toMatchSnapshot();
+ expect(createDOMProps('li', resolver, {}).className).toMatchSnapshot();
+ expect(createDOMProps('ul', resolver, {}).className).toMatchSnapshot();
});
});
diff --git a/packages/react-native-web/src/modules/createDOMProps/index.js b/packages/react-native-web/src/modules/createDOMProps/index.js
index 3f7efb13fe..b6af1d95e9 100644
--- a/packages/react-native-web/src/modules/createDOMProps/index.js
+++ b/packages/react-native-web/src/modules/createDOMProps/index.js
@@ -10,7 +10,6 @@
import AccessibilityUtil from '../AccessibilityUtil';
import css from '../../exports/StyleSheet/css';
import StyleSheet from '../../exports/StyleSheet';
-import styleResolver from '../../exports/StyleSheet/styleResolver';
import { STYLE_GROUPS } from '../../exports/StyleSheet/constants';
const emptyObject = {};
@@ -62,7 +61,7 @@ const pointerEventsStyles = StyleSheet.create({
}
});
-const createDOMProps = (elementType, props) => {
+const createDOMProps = (elementType, styleResolver, props) => {
if (!props) {
props = emptyObject;
}
diff --git a/packages/react-native-web/src/modules/usePlatformMethods/index.js b/packages/react-native-web/src/modules/usePlatformMethods/index.js
index c04c781c72..e0569f6271 100644
--- a/packages/react-native-web/src/modules/usePlatformMethods/index.js
+++ b/packages/react-native-web/src/modules/usePlatformMethods/index.js
@@ -13,13 +13,22 @@ import type { ViewProps } from '../../exports/View';
import UIManager from '../../exports/UIManager';
import createDOMProps from '../createDOMProps';
import useStable from '../useStable';
-import { useRef } from 'react';
+import { useContext, useRef } from 'react';
+import RootContext from '../../exports/AppRegistry/RootContext';
const emptyObject = {};
-function setNativeProps(node, nativeProps, classList, pointerEvents, style, previousStyleRef) {
+function setNativeProps(
+ node,
+ styleResolver,
+ nativeProps,
+ classList,
+ pointerEvents,
+ style,
+ previousStyleRef
+) {
if (node != null && nativeProps) {
- const domProps = createDOMProps(null, {
+ const domProps = createDOMProps(null, styleResolver, {
pointerEvents,
...nativeProps,
classList: [classList, nativeProps.className],
@@ -60,6 +69,7 @@ export default function usePlatformMethods({
}): (hostNode: any) => void {
const previousStyleRef = useRef(null);
const setNativePropsArgsRef = useRef(null);
+ const rootContext = useContext(RootContext);
setNativePropsArgsRef.current = { classList, pointerEvents, style };
// Avoid creating a new ref on every render. The props only need to be
@@ -72,7 +82,15 @@ export default function usePlatformMethods({
hostNode.measureInWindow = (callback) => UIManager.measureInWindow(hostNode, callback);
hostNode.setNativeProps = (nativeProps) => {
const { classList, style, pointerEvents } = setNativePropsArgsRef.current || emptyObject;
- setNativeProps(hostNode, nativeProps, classList, pointerEvents, style, previousStyleRef);
+ setNativeProps(
+ hostNode,
+ rootContext.styleResolver,
+ nativeProps,
+ classList,
+ pointerEvents,
+ style,
+ previousStyleRef
+ );
};
}
});
diff --git a/packages/react-native-web/src/modules/usePressEvents/PressResponder.js b/packages/react-native-web/src/modules/usePressEvents/PressResponder.js
index 1de4323bd5..e3898db464 100644
--- a/packages/react-native-web/src/modules/usePressEvents/PressResponder.js
+++ b/packages/react-native-web/src/modules/usePressEvents/PressResponder.js
@@ -10,6 +10,8 @@
'use strict';
+import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
+
type ClickEvent = any;
type KeyboardEvent = any;
type ResponderEvent = any;
@@ -205,7 +207,7 @@ const DEFAULT_PRESS_DELAY_MS = 50;
* ▼
* ┌─────────────────────┐ ┌───────────────────┐ ┌───────────────────┐
* │ RESPONDER_INACTIVE_ │ DELAY │ RESPONDER_ACTIVE_ │ T + DELAY │ RESPONDER_ACTIVE_ │
- * │ PRESS_START ├────────▶ │ PRESS_START ├────────────▶ │ LONG_PRESS_START │
+ * │ PRESS_START ├────────▶│ PRESS_START ├────────────▶│ LONG_PRESS_START │
* └─────────────────────┘ └───────────────────┘ └───────────────────┘
*
* T + DELAY => LONG_PRESS_DELAY + DELAY
@@ -228,13 +230,17 @@ export default class PressResponder {
pageY: number
|}>;
_touchState: TouchState = NOT_RESPONDER;
+ _document: ?Document;
- constructor(config: PressResponderConfig) {
- this.configure(config);
+ constructor(config: PressResponderConfig, doc?: Document) {
+ this.configure(config, doc);
}
- configure(config: PressResponderConfig): void {
+ configure(config: PressResponderConfig, doc?: Document): void {
this._config = config;
+ if (canUseDOM) {
+ this._document = doc ?? document;
+ }
}
/**
@@ -303,7 +309,7 @@ export default class PressResponder {
if (this._touchState !== NOT_RESPONDER && isValidKeyPress(event)) {
end(event);
- document.removeEventListener('keyup', keyupHandler);
+ this._document.removeEventListener('keyup', keyupHandler);
const role = target.getAttribute('role');
const elementType = target.tagName.toLowerCase();
@@ -342,7 +348,7 @@ export default class PressResponder {
start(event, false);
// Listen to 'keyup' on document to account for situations where
// focus is moved to another element during 'keydown'.
- document.addEventListener('keyup', keyupHandler);
+ this._document.addEventListener('keyup', keyupHandler);
}
const role = target.getAttribute('role');
const isSpacebarKey = key === ' ' || key === 'Spacebar';
diff --git a/packages/react-native-web/src/modules/usePressEvents/index.js b/packages/react-native-web/src/modules/usePressEvents/index.js
index 6555be62fc..e6637aede9 100644
--- a/packages/react-native-web/src/modules/usePressEvents/index.js
+++ b/packages/react-native-web/src/modules/usePressEvents/index.js
@@ -11,21 +11,23 @@
'use strict';
import type { EventHandlers, PressResponderConfig } from './PressResponder';
+import RootContext from '../../exports/AppRegistry/RootContext';
import PressResponder from './PressResponder';
-import { useDebugValue, useEffect, useRef } from 'react';
+import { useContext, useDebugValue, useEffect, useRef } from 'react';
export default function usePressEvents(hostRef: any, config: PressResponderConfig): EventHandlers {
const pressResponderRef = useRef(null);
+ const rootContext = useContext(RootContext);
if (pressResponderRef.current == null) {
- pressResponderRef.current = new PressResponder(config);
+ pressResponderRef.current = new PressResponder(config, rootContext.rootTag?.ownerDocument);
}
const pressResponder = pressResponderRef.current;
// Re-configure to use the current node and configuration.
useEffect(() => {
- pressResponder.configure(config);
- }, [config, pressResponder]);
+ pressResponder.configure(config, rootContext.rootTag?.ownerDocument);
+ }, [config, pressResponder, rootContext.rootTag]);
// Reset the `pressResponder` when cleanup needs to occur. This is
// a separate effect because we do not want to rest the responder when `config` changes.
diff --git a/packages/react-native-web/src/modules/useResponderEvents/ResponderSystem.js b/packages/react-native-web/src/modules/useResponderEvents/ResponderSystem.js
index b9aaad7036..59c426aa21 100644
--- a/packages/react-native-web/src/modules/useResponderEvents/ResponderSystem.js
+++ b/packages/react-native-web/src/modules/useResponderEvents/ResponderSystem.js
@@ -10,7 +10,7 @@
/**
* RESPONDER EVENT SYSTEM
*
- * A single, global "interaction lock" on views. For a view to be the "responder" means
+ * A single, global window "interaction lock" on views. For a view to be the "responder" means
* that pointer interactions are exclusive to that view and none other. The "interaction
* lock" can be transferred (only) to ancestors of the current "responder" as long as
* pointers continue to be active.
@@ -223,283 +223,279 @@ const shouldSetResponderEvents = {
};
const emptyResponder = { id: null, idPath: null, node: null };
-const responderListenersMap = new Map();
-let isEmulatingMouseEvents = false;
-let trackedTouchCount = 0;
-let currentResponder: ResponderInstance = {
- id: null,
- node: null,
- idPath: null
-};
-
-function changeCurrentResponder(responder: ResponderInstance) {
- currentResponder = responder;
-}
-
-function getResponderConfig(id: ResponderId): ResponderConfig | Object {
- const config = responderListenersMap.get(id);
- return config != null ? config : emptyObject;
-}
-
-/**
- * Process native events
- *
- * A single event listener is used to manage the responder system.
- * All pointers are tracked in the ResponderTouchHistoryStore. Native events
- * are interpreted in terms of the Responder System and checked to see if
- * the responder should be transferred. Each host node that is attached to
- * the Responder System has an ID, which is used to look up its associated
- * callbacks.
- */
-function eventListener(domEvent: any) {
- const eventType = domEvent.type;
- const eventTarget = domEvent.target;
-
- /**
- * Manage emulated events and early bailout.
- * Since PointerEvent is not used yet (lack of support in older Safari), it's
- * necessary to manually manage the mess of browser touch/mouse events.
- * And bailout early for termination events when there is no active responder.
- */
-
- // Flag when browser may produce emulated events
- if (eventType === 'touchstart') {
- isEmulatingMouseEvents = true;
- }
- // Remove flag when browser will not produce emulated events
- if (eventType === 'touchmove' || trackedTouchCount > 1) {
- isEmulatingMouseEvents = false;
- }
- // Ignore various events in particular circumstances
- if (
- // Ignore browser emulated mouse events
- (eventType === 'mousedown' && isEmulatingMouseEvents) ||
- (eventType === 'mousemove' && isEmulatingMouseEvents) ||
- // Ignore mousemove if a mousedown didn't occur first
- (eventType === 'mousemove' && trackedTouchCount < 1)
- ) {
- return;
- }
- // Remove flag after emulated events are finished
- if (isEmulatingMouseEvents && eventType === 'mouseup') {
- if (trackedTouchCount === 0) {
- isEmulatingMouseEvents = false;
+export default class ResponderSystem {
+ _window: Window;
+ _responderListenersMap = new Map();
+ _isEmulatingMouseEvents = false;
+ _trackedTouchCount = 0;
+ _currentResponder: ResponderInstance = {
+ id: null,
+ node: null,
+ idPath: null
+ };
+ _responderTouchHistoryStore = new ResponderTouchHistoryStore();
+
+ constructor(win: ?Window) {
+ if (canUseDOM) {
+ this._window = win ?? window;
}
- return;
}
- const isStartEvent = isStartish(eventType) && isPrimaryPointerDown(domEvent);
- const isMoveEvent = isMoveish(eventType);
- const isEndEvent = isEndish(eventType);
- const isScrollEvent = isScroll(eventType);
- const isSelectionChangeEvent = isSelectionChange(eventType);
- const responderEvent = createResponderEvent(domEvent);
-
- /**
- * Record the state of active pointers
- */
+ _changeCurrentResponder(responder: ResponderInstance) {
+ this._currentResponder = responder;
+ }
- if (isStartEvent || isMoveEvent || isEndEvent) {
- if (domEvent.touches) {
- trackedTouchCount = domEvent.touches.length;
- } else {
- if (isStartEvent) {
- trackedTouchCount = 1;
- } else if (isEndEvent) {
- trackedTouchCount = 0;
- }
- }
- ResponderTouchHistoryStore.recordTouchTrack(eventType, responderEvent.nativeEvent);
+ _getResponderConfig(id: ResponderId): ResponderConfig | Object {
+ const config = this._responderListenersMap.get(id);
+ return config != null ? config : emptyObject;
}
/**
- * Responder System logic
+ * Process native events
+ *
+ * A single event listener is used to manage the responder system.
+ * All pointers are tracked in the ResponderTouchHistoryStore. Native events
+ * are interpreted in terms of the Responder System and checked to see if
+ * the responder should be transferred. Each host node that is attached to
+ * the Responder System has an ID, which is used to look up its associated
+ * callbacks.
*/
-
- let eventPaths = getResponderPaths(domEvent);
- let wasNegotiated = false;
- let wantsResponder;
-
- // If an event occured that might change the current responder...
- if (isStartEvent || isMoveEvent || (isScrollEvent && trackedTouchCount > 0)) {
- // If there is already a responder, prune the event paths to the lowest common ancestor
- // of the existing responder and deepest target of the event.
- const currentResponderIdPath = currentResponder.idPath;
- const eventIdPath = eventPaths.idPath;
-
- if (currentResponderIdPath != null && eventIdPath != null) {
- const lowestCommonAncestor = getLowestCommonAncestor(currentResponderIdPath, eventIdPath);
- if (lowestCommonAncestor != null) {
- const indexOfLowestCommonAncestor = eventIdPath.indexOf(lowestCommonAncestor);
- // Skip the current responder so it doesn't receive unexpected "shouldSet" events.
- const index =
- indexOfLowestCommonAncestor + (lowestCommonAncestor === currentResponder.id ? 1 : 0);
- eventPaths = {
- idPath: eventIdPath.slice(index),
- nodePath: eventPaths.nodePath.slice(index)
- };
- } else {
- eventPaths = null;
- }
+ _eventListener = (domEvent: any) => {
+ const eventType = domEvent.type;
+ const eventTarget = domEvent.target;
+
+ /**
+ * Manage emulated events and early bailout.
+ * Since PointerEvent is not used yet (lack of support in older Safari), it's
+ * necessary to manually manage the mess of browser touch/mouse events.
+ * And bailout early for termination events when there is no active responder.
+ */
+
+ // Flag when browser may produce emulated events
+ if (eventType === 'touchstart') {
+ this._isEmulatingMouseEvents = true;
}
-
- if (eventPaths != null) {
- // If a node wants to become the responder, attempt to transfer.
- wantsResponder = findWantsResponder(eventPaths, domEvent, responderEvent);
- if (wantsResponder != null) {
- // Sets responder if none exists, or negotates with existing responder.
- attemptTransfer(responderEvent, wantsResponder);
- wasNegotiated = true;
+ // Remove flag when browser will not produce emulated events
+ if (eventType === 'touchmove' || this._trackedTouchCount > 1) {
+ this._isEmulatingMouseEvents = false;
+ }
+ // Ignore various events in particular circumstances
+ if (
+ // Ignore browser emulated mouse events
+ (eventType === 'mousedown' && this._isEmulatingMouseEvents) ||
+ (eventType === 'mousemove' && this._isEmulatingMouseEvents) ||
+ // Ignore mousemove if a mousedown didn't occur first
+ (eventType === 'mousemove' && this._trackedTouchCount < 1)
+ ) {
+ return;
+ }
+ // Remove flag after emulated events are finished
+ if (this._isEmulatingMouseEvents && eventType === 'mouseup') {
+ if (this._trackedTouchCount === 0) {
+ this._isEmulatingMouseEvents = false;
}
+ return;
}
- }
- // If there is now a responder, invoke its callbacks for the lifecycle of the gesture.
- if (currentResponder.id != null && currentResponder.node != null) {
- const { id, node } = currentResponder;
- const {
- onResponderStart,
- onResponderMove,
- onResponderEnd,
- onResponderRelease,
- onResponderTerminate,
- onResponderTerminationRequest
- } = getResponderConfig(id);
+ const isStartEvent = isStartish(eventType) && isPrimaryPointerDown(domEvent);
+ const isMoveEvent = isMoveish(eventType);
+ const isEndEvent = isEndish(eventType);
+ const isScrollEvent = isScroll(eventType);
+ const isSelectionChangeEvent = isSelectionChange(eventType);
+ const responderEvent = createResponderEvent(domEvent, this._responderTouchHistoryStore);
- responderEvent.bubbles = false;
- responderEvent.cancelable = false;
- responderEvent.currentTarget = node;
+ /**
+ * Record the state of active pointers
+ */
- // Start
- if (isStartEvent) {
- if (onResponderStart != null) {
- responderEvent.dispatchConfig.registrationName = 'onResponderStart';
- onResponderStart(responderEvent);
+ if (isStartEvent || isMoveEvent || isEndEvent) {
+ if (domEvent.touches) {
+ this._trackedTouchCount = domEvent.touches.length;
+ } else {
+ if (isStartEvent) {
+ this._trackedTouchCount = 1;
+ } else if (isEndEvent) {
+ this._trackedTouchCount = 0;
+ }
}
+ this._responderTouchHistoryStore.recordTouchTrack(eventType, responderEvent.nativeEvent);
}
- // Move
- else if (isMoveEvent) {
- if (onResponderMove != null) {
- responderEvent.dispatchConfig.registrationName = 'onResponderMove';
- onResponderMove(responderEvent);
- }
- } else {
- const isTerminateEvent =
- isCancelish(eventType) ||
- // native context menu
- eventType === 'contextmenu' ||
- // window blur
- (eventType === 'blur' && eventTarget === window) ||
- // responder (or ancestors) blur
- (eventType === 'blur' && eventTarget.contains(node) && domEvent.relatedTarget !== node) ||
- // native scroll without using a pointer
- (isScrollEvent && trackedTouchCount === 0) ||
- // native scroll on node that is parent of the responder (allow siblings to scroll)
- (isScrollEvent && eventTarget.contains(node) && eventTarget !== node) ||
- // native select/selectionchange on node
- (isSelectionChangeEvent && hasValidSelection(domEvent));
-
- const isReleaseEvent =
- isEndEvent && !isTerminateEvent && !hasTargetTouches(node, domEvent.touches);
-
- // End
- if (isEndEvent) {
- if (onResponderEnd != null) {
- responderEvent.dispatchConfig.registrationName = 'onResponderEnd';
- onResponderEnd(responderEvent);
+
+ /**
+ * Responder System logic
+ */
+
+ let eventPaths = getResponderPaths(domEvent);
+ let wasNegotiated = false;
+ let wantsResponder;
+
+ // If an event occured that might change the current responder...
+ if (isStartEvent || isMoveEvent || (isScrollEvent && this._trackedTouchCount > 0)) {
+ // If there is already a responder, prune the event paths to the lowest common ancestor
+ // of the existing responder and deepest target of the event.
+ const currentResponderIdPath = this._currentResponder.idPath;
+ const eventIdPath = eventPaths.idPath;
+
+ if (currentResponderIdPath != null && eventIdPath != null) {
+ const lowestCommonAncestor = getLowestCommonAncestor(currentResponderIdPath, eventIdPath);
+ if (lowestCommonAncestor != null) {
+ const indexOfLowestCommonAncestor = eventIdPath.indexOf(lowestCommonAncestor);
+ // Skip the current responder so it doesn't receive unexpected "shouldSet" events.
+ const index =
+ indexOfLowestCommonAncestor +
+ (lowestCommonAncestor === this._currentResponder.id ? 1 : 0);
+ eventPaths = {
+ idPath: eventIdPath.slice(index),
+ nodePath: eventPaths.nodePath.slice(index)
+ };
+ } else {
+ eventPaths = null;
}
}
- // Release
- if (isReleaseEvent) {
- if (onResponderRelease != null) {
- responderEvent.dispatchConfig.registrationName = 'onResponderRelease';
- onResponderRelease(responderEvent);
+
+ if (eventPaths != null) {
+ // If a node wants to become the responder, attempt to transfer.
+ wantsResponder = this._findWantsResponder(eventPaths, domEvent, responderEvent);
+ if (wantsResponder != null) {
+ // Sets responder if none exists, or negotates with existing responder.
+ this._attemptTransfer(responderEvent, wantsResponder);
+ wasNegotiated = true;
}
- changeCurrentResponder(emptyResponder);
}
- // Terminate
- if (isTerminateEvent) {
- let shouldTerminate = true;
+ }
+
+ // If there is now a responder, invoke its callbacks for the lifecycle of the gesture.
+ if (this._currentResponder.id != null && this._currentResponder.node != null) {
+ const { id, node } = this._currentResponder;
+ const {
+ onResponderStart,
+ onResponderMove,
+ onResponderEnd,
+ onResponderRelease,
+ onResponderTerminate,
+ onResponderTerminationRequest
+ } = this._getResponderConfig(id);
+
+ responderEvent.bubbles = false;
+ responderEvent.cancelable = false;
+ responderEvent.currentTarget = node;
- // Responders can still avoid termination but only for these events.
- if (
+ // Start
+ if (isStartEvent) {
+ if (onResponderStart != null) {
+ responderEvent.dispatchConfig.registrationName = 'onResponderStart';
+ onResponderStart(responderEvent);
+ }
+ }
+ // Move
+ else if (isMoveEvent) {
+ if (onResponderMove != null) {
+ responderEvent.dispatchConfig.registrationName = 'onResponderMove';
+ onResponderMove(responderEvent);
+ }
+ } else {
+ const isTerminateEvent =
+ isCancelish(eventType) ||
+ // native context menu
eventType === 'contextmenu' ||
- eventType === 'scroll' ||
- eventType === 'selectionchange'
- ) {
- // Only call this function is it wasn't already called during negotiation.
- if (wasNegotiated) {
- shouldTerminate = false;
- } else if (onResponderTerminationRequest != null) {
- responderEvent.dispatchConfig.registrationName = 'onResponderTerminationRequest';
- if (onResponderTerminationRequest(responderEvent) === false) {
+ // window blur
+ (eventType === 'blur' && eventTarget === this._window) ||
+ // responder (or ancestors) blur
+ (eventType === 'blur' && eventTarget.contains(node) && domEvent.relatedTarget !== node) ||
+ // native scroll without using a pointer
+ (isScrollEvent && this._trackedTouchCount === 0) ||
+ // native scroll on node that is parent of the responder (allow siblings to scroll)
+ (isScrollEvent && eventTarget.contains(node) && eventTarget !== node) ||
+ // native select/selectionchange on node
+ (isSelectionChangeEvent && hasValidSelection(domEvent));
+
+ const isReleaseEvent =
+ isEndEvent && !isTerminateEvent && !hasTargetTouches(node, domEvent.touches);
+
+ // End
+ if (isEndEvent) {
+ if (onResponderEnd != null) {
+ responderEvent.dispatchConfig.registrationName = 'onResponderEnd';
+ onResponderEnd(responderEvent);
+ }
+ }
+ // Release
+ if (isReleaseEvent) {
+ if (onResponderRelease != null) {
+ responderEvent.dispatchConfig.registrationName = 'onResponderRelease';
+ onResponderRelease(responderEvent);
+ }
+ this._changeCurrentResponder(emptyResponder);
+ }
+ // Terminate
+ if (isTerminateEvent) {
+ let shouldTerminate = true;
+
+ // Responders can still avoid termination but only for these events.
+ if (
+ eventType === 'contextmenu' ||
+ eventType === 'scroll' ||
+ eventType === 'selectionchange'
+ ) {
+ // Only call this function is it wasn't already called during negotiation.
+ if (wasNegotiated) {
shouldTerminate = false;
+ } else if (onResponderTerminationRequest != null) {
+ responderEvent.dispatchConfig.registrationName = 'onResponderTerminationRequest';
+ if (onResponderTerminationRequest(responderEvent) === false) {
+ shouldTerminate = false;
+ }
}
}
- }
- if (shouldTerminate) {
- if (onResponderTerminate != null) {
- responderEvent.dispatchConfig.registrationName = 'onResponderTerminate';
- onResponderTerminate(responderEvent);
+ if (shouldTerminate) {
+ if (onResponderTerminate != null) {
+ responderEvent.dispatchConfig.registrationName = 'onResponderTerminate';
+ onResponderTerminate(responderEvent);
+ }
+ this._changeCurrentResponder(emptyResponder);
+ this._isEmulatingMouseEvents = false;
+ this._trackedTouchCount = 0;
}
- changeCurrentResponder(emptyResponder);
- isEmulatingMouseEvents = false;
- trackedTouchCount = 0;
}
}
}
- }
-}
-
-/**
- * Walk the event path to/from the target node. At each node, stop and call the
- * relevant "shouldSet" functions for the given event type. If any of those functions
- * call "stopPropagation" on the event, stop searching for a responder.
- */
-function findWantsResponder(eventPaths, domEvent, responderEvent) {
- const shouldSetCallbacks = shouldSetResponderEvents[(domEvent.type: any)]; // for Flow
-
- if (shouldSetCallbacks != null) {
- const { idPath, nodePath } = eventPaths;
-
- const shouldSetCallbackCaptureName = shouldSetCallbacks[0];
- const shouldSetCallbackBubbleName = shouldSetCallbacks[1];
- const { bubbles } = shouldSetCallbacks[2];
+ };
- const check = function (id, node, callbackName) {
- const config = getResponderConfig(id);
- const shouldSetCallback = config[callbackName];
- if (shouldSetCallback != null) {
- responderEvent.currentTarget = node;
- if (shouldSetCallback(responderEvent) === true) {
- // Start the path from the potential responder
- const prunedIdPath = idPath.slice(idPath.indexOf(id));
- return { id, node, idPath: prunedIdPath };
+ /**
+ * Walk the event path to/from the target node. At each node, stop and call the
+ * relevant "shouldSet" functions for the given event type. If any of those functions
+ * call "stopPropagation" on the event, stop searching for a responder.
+ */
+ _findWantsResponder(eventPaths, domEvent, responderEvent) {
+ const shouldSetCallbacks = shouldSetResponderEvents[(domEvent.type: any)]; // for Flow
+
+ if (shouldSetCallbacks != null) {
+ const { idPath, nodePath } = eventPaths;
+
+ const shouldSetCallbackCaptureName = shouldSetCallbacks[0];
+ const shouldSetCallbackBubbleName = shouldSetCallbacks[1];
+ const { bubbles } = shouldSetCallbacks[2];
+
+ const check = (id, node, callbackName) => {
+ const config = this._getResponderConfig(id);
+ const shouldSetCallback = config[callbackName];
+ if (shouldSetCallback != null) {
+ responderEvent.currentTarget = node;
+ if (shouldSetCallback(responderEvent) === true) {
+ // Start the path from the potential responder
+ const prunedIdPath = idPath.slice(idPath.indexOf(id));
+ return { id, node, idPath: prunedIdPath };
+ }
}
- }
- };
-
- // capture
- for (let i = idPath.length - 1; i >= 0; i--) {
- const id = idPath[i];
- const node = nodePath[i];
- const result = check(id, node, shouldSetCallbackCaptureName);
- if (result != null) {
- return result;
- }
- if (responderEvent.isPropagationStopped() === true) {
- return;
- }
- }
+ };
- // bubble
- if (bubbles) {
- for (let i = 0; i < idPath.length; i++) {
+ // capture
+ for (let i = idPath.length - 1; i >= 0; i--) {
const id = idPath[i];
const node = nodePath[i];
- const result = check(id, node, shouldSetCallbackBubbleName);
+ const result = check(id, node, shouldSetCallbackCaptureName);
if (result != null) {
return result;
}
@@ -507,159 +503,177 @@ function findWantsResponder(eventPaths, domEvent, responderEvent) {
return;
}
}
- } else {
- const id = idPath[0];
- const node = nodePath[0];
- const target = domEvent.target;
- if (target === node) {
- return check(id, node, shouldSetCallbackBubbleName);
+
+ // bubble
+ if (bubbles) {
+ for (let i = 0; i < idPath.length; i++) {
+ const id = idPath[i];
+ const node = nodePath[i];
+ const result = check(id, node, shouldSetCallbackBubbleName);
+ if (result != null) {
+ return result;
+ }
+ if (responderEvent.isPropagationStopped() === true) {
+ return;
+ }
+ }
+ } else {
+ const id = idPath[0];
+ const node = nodePath[0];
+ const target = domEvent.target;
+ if (target === node) {
+ return check(id, node, shouldSetCallbackBubbleName);
+ }
}
}
}
-}
-/**
- * Attempt to transfer the responder.
- */
-function attemptTransfer(responderEvent: ResponderEvent, wantsResponder: ActiveResponderInstance) {
- const { id: currentId, node: currentNode } = currentResponder;
- const { id, node } = wantsResponder;
-
- const { onResponderGrant, onResponderReject } = getResponderConfig(id);
+ /**
+ * Attempt to transfer the responder.
+ */
+ _attemptTransfer(responderEvent: ResponderEvent, wantsResponder: ActiveResponderInstance) {
+ const { id: currentId, node: currentNode } = this._currentResponder;
+ const { id, node } = wantsResponder;
- responderEvent.bubbles = false;
- responderEvent.cancelable = false;
- responderEvent.currentTarget = node;
+ const { onResponderGrant, onResponderReject } = this._getResponderConfig(id);
- // Set responder
- if (currentId == null) {
- if (onResponderGrant != null) {
- responderEvent.currentTarget = node;
- responderEvent.dispatchConfig.registrationName = 'onResponderGrant';
- onResponderGrant(responderEvent);
- }
- changeCurrentResponder(wantsResponder);
- }
- // Negotiate with current responder
- else {
- const { onResponderTerminate, onResponderTerminationRequest } = getResponderConfig(currentId);
-
- let allowTransfer = true;
- if (onResponderTerminationRequest != null) {
- responderEvent.currentTarget = currentNode;
- responderEvent.dispatchConfig.registrationName = 'onResponderTerminationRequest';
- if (onResponderTerminationRequest(responderEvent) === false) {
- allowTransfer = false;
- }
- }
+ responderEvent.bubbles = false;
+ responderEvent.cancelable = false;
+ responderEvent.currentTarget = node;
- if (allowTransfer) {
- // Terminate existing responder
- if (onResponderTerminate != null) {
- responderEvent.currentTarget = currentNode;
- responderEvent.dispatchConfig.registrationName = 'onResponderTerminate';
- onResponderTerminate(responderEvent);
- }
- // Grant next responder
+ // Set responder
+ if (currentId == null) {
if (onResponderGrant != null) {
responderEvent.currentTarget = node;
responderEvent.dispatchConfig.registrationName = 'onResponderGrant';
onResponderGrant(responderEvent);
}
- changeCurrentResponder(wantsResponder);
- } else {
- // Reject responder request
- if (onResponderReject != null) {
- responderEvent.currentTarget = node;
- responderEvent.dispatchConfig.registrationName = 'onResponderReject';
- onResponderReject(responderEvent);
+ this._changeCurrentResponder(wantsResponder);
+ }
+ // Negotiate with current responder
+ else {
+ const { onResponderTerminate, onResponderTerminationRequest } = this._getResponderConfig(
+ currentId
+ );
+
+ let allowTransfer = true;
+ if (onResponderTerminationRequest != null) {
+ responderEvent.currentTarget = currentNode;
+ responderEvent.dispatchConfig.registrationName = 'onResponderTerminationRequest';
+ if (onResponderTerminationRequest(responderEvent) === false) {
+ allowTransfer = false;
+ }
+ }
+
+ if (allowTransfer) {
+ // Terminate existing responder
+ if (onResponderTerminate != null) {
+ responderEvent.currentTarget = currentNode;
+ responderEvent.dispatchConfig.registrationName = 'onResponderTerminate';
+ onResponderTerminate(responderEvent);
+ }
+ // Grant next responder
+ if (onResponderGrant != null) {
+ responderEvent.currentTarget = node;
+ responderEvent.dispatchConfig.registrationName = 'onResponderGrant';
+ onResponderGrant(responderEvent);
+ }
+ this._changeCurrentResponder(wantsResponder);
+ } else {
+ // Reject responder request
+ if (onResponderReject != null) {
+ responderEvent.currentTarget = node;
+ responderEvent.dispatchConfig.registrationName = 'onResponderReject';
+ onResponderReject(responderEvent);
+ }
}
}
}
-}
-/* ------------ PUBLIC API ------------ */
+ /* ------------ PUBLIC API ------------ */
-/**
- * Attach Listeners
- *
- * Use native events as ReactDOM doesn't have a non-plugin API to implement
- * this system.
- */
-const documentEventsCapturePhase = ['blur', 'scroll'];
-const documentEventsBubblePhase = [
- // mouse
- 'mousedown',
- 'mousemove',
- 'mouseup',
- 'dragstart',
- // touch
- 'touchstart',
- 'touchmove',
- 'touchend',
- 'touchcancel',
- // other
- 'contextmenu',
- 'select',
- 'selectionchange'
-];
-export function attachListeners() {
- if (canUseDOM && window.__reactResponderSystemActive == null) {
- window.addEventListener('blur', eventListener);
- documentEventsBubblePhase.forEach((eventType) => {
- document.addEventListener(eventType, eventListener);
- });
- documentEventsCapturePhase.forEach((eventType) => {
- document.addEventListener(eventType, eventListener, true);
- });
- window.__reactResponderSystemActive = true;
+ /**
+ * Attach Listeners
+ *
+ * Use native events as ReactDOM doesn't have a non-plugin API to implement
+ * this system.
+ */
+ static documentEventsCapturePhase = ['blur', 'scroll'];
+ static documentEventsBubblePhase = [
+ // mouse
+ 'mousedown',
+ 'mousemove',
+ 'mouseup',
+ 'dragstart',
+ // touch
+ 'touchstart',
+ 'touchmove',
+ 'touchend',
+ 'touchcancel',
+ // other
+ 'contextmenu',
+ 'select',
+ 'selectionchange'
+ ];
+
+ attachListeners() {
+ if (canUseDOM && this._window.__reactResponderSystemActive == null) {
+ this._window.addEventListener('blur', this._eventListener);
+ ResponderSystem.documentEventsBubblePhase.forEach((eventType) => {
+ this._window.document.addEventListener(eventType, this._eventListener);
+ });
+ ResponderSystem.documentEventsCapturePhase.forEach((eventType) => {
+ this._window.document.addEventListener(eventType, this._eventListener, true);
+ });
+ this._window.__reactResponderSystemActive = true;
+ }
}
-}
-/**
- * Register a node with the ResponderSystem.
- */
-export function addNode(id: ResponderId, node: any, config: ResponderConfig) {
- setResponderId(node, id);
- responderListenersMap.set(id, config);
-}
-
-/**
- * Unregister a node with the ResponderSystem.
- */
-export function removeNode(id: ResponderId) {
- if (currentResponder.id === id) {
- terminateResponder();
+ /**
+ * Register a node with the ResponderSystem.
+ */
+ addNode(id: ResponderId, node: any, config: ResponderConfig) {
+ setResponderId(node, id);
+ this._responderListenersMap.set(id, config);
}
- if (responderListenersMap.has(id)) {
- responderListenersMap.delete(id);
+
+ /**
+ * Unregister a node with the ResponderSystem.
+ */
+ removeNode(id: ResponderId) {
+ if (this._currentResponder.id === id) {
+ this.terminateResponder();
+ }
+ if (this._responderListenersMap.has(id)) {
+ this._responderListenersMap.delete(id);
+ }
}
-}
-/**
- * Allow the current responder to be terminated from within components to support
- * more complex requirements, such as use with other React libraries for working
- * with scroll views, input views, etc.
- */
-export function terminateResponder() {
- const { id, node } = currentResponder;
- if (id != null && node != null) {
- const { onResponderTerminate } = getResponderConfig(id);
- if (onResponderTerminate != null) {
- const event = createResponderEvent({});
- event.currentTarget = node;
- onResponderTerminate(event);
+ /**
+ * Allow the current responder to be terminated from within components to support
+ * more complex requirements, such as use with other React libraries for working
+ * with scroll views, input views, etc.
+ */
+ terminateResponder() {
+ const { id, node } = this._currentResponder;
+ if (id != null && node != null) {
+ const { onResponderTerminate } = this._getResponderConfig(id);
+ if (onResponderTerminate != null) {
+ const event = createResponderEvent({}, this._responderTouchHistoryStore);
+ event.currentTarget = node;
+ onResponderTerminate(event);
+ }
+ this._changeCurrentResponder(emptyResponder);
}
- changeCurrentResponder(emptyResponder);
+ this._isEmulatingMouseEvents = false;
+ this._trackedTouchCount = 0;
}
- isEmulatingMouseEvents = false;
- trackedTouchCount = 0;
-}
-/**
- * Allow unit tests to inspect the current responder in the system.
- * FOR TESTING ONLY.
- */
-export function getResponderNode(): any {
- return currentResponder.node;
+ /**
+ * Allow unit tests to inspect the current responder in the system.
+ * FOR TESTING ONLY.
+ */
+ getResponderNode(): any {
+ return this._currentResponder.node;
+ }
}
diff --git a/packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js b/packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js
index 07d97dc022..fa1476ac58 100644
--- a/packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js
+++ b/packages/react-native-web/src/modules/useResponderEvents/ResponderTouchHistoryStore.js
@@ -31,16 +31,6 @@ type TouchRecord = {|
const __DEV__ = process.env.NODE_ENV !== 'production';
const MAX_TOUCH_BANK = 20;
-const touchBank: Array = [];
-const touchHistory = {
- touchBank,
- numberActiveTouches: 0,
- // If there is only one active touch, we remember its location. This prevents
- // us having to loop through all of the touches all the time in the most
- // common case.
- indexOfSingleActiveTouch: -1,
- mostRecentTimeStamp: 0
-};
function timestampForTouch(touch: Touch): number {
// The legacy internal implementation provides "timeStamp", which has been
@@ -97,106 +87,117 @@ function getTouchIdentifier({ identifier }: Touch): number {
return identifier;
}
-function recordTouchStart(touch: Touch): void {
- const identifier = getTouchIdentifier(touch);
- const touchRecord = touchBank[identifier];
- if (touchRecord) {
- resetTouchRecord(touchRecord, touch);
- } else {
- touchBank[identifier] = createTouchRecord(touch);
- }
- touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
-}
-
-function recordTouchMove(touch: Touch): void {
- const touchRecord = touchBank[getTouchIdentifier(touch)];
- if (touchRecord) {
- touchRecord.touchActive = true;
- touchRecord.previousPageX = touchRecord.currentPageX;
- touchRecord.previousPageY = touchRecord.currentPageY;
- touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
- touchRecord.currentPageX = touch.pageX;
- touchRecord.currentPageY = touch.pageY;
- touchRecord.currentTimeStamp = timestampForTouch(touch);
- touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
- } else {
- console.warn(
- 'Cannot record touch move without a touch start.\n',
- `Touch Move: ${printTouch(touch)}\n`,
- `Touch Bank: ${printTouchBank()}`
- );
- }
-}
-
-function recordTouchEnd(touch: Touch): void {
- const touchRecord = touchBank[getTouchIdentifier(touch)];
- if (touchRecord) {
- touchRecord.touchActive = false;
- touchRecord.previousPageX = touchRecord.currentPageX;
- touchRecord.previousPageY = touchRecord.currentPageY;
- touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
- touchRecord.currentPageX = touch.pageX;
- touchRecord.currentPageY = touch.pageY;
- touchRecord.currentTimeStamp = timestampForTouch(touch);
- touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
- } else {
- console.warn(
- 'Cannot record touch end without a touch start.\n',
- `Touch End: ${printTouch(touch)}\n`,
- `Touch Bank: ${printTouchBank()}`
- );
- }
-}
-
-function printTouch(touch: Touch): string {
- return JSON.stringify({
- identifier: touch.identifier,
- pageX: touch.pageX,
- pageY: touch.pageY,
- timestamp: timestampForTouch(touch)
- });
-}
-
-function printTouchBank(): string {
- let printed = JSON.stringify(touchBank.slice(0, MAX_TOUCH_BANK));
- if (touchBank.length > MAX_TOUCH_BANK) {
- printed += ' (original size: ' + touchBank.length + ')';
- }
- return printed;
-}
+export default class ResponderTouchHistoryStore {
+ _touchBank: Array = [];
+ _touchHistory = {
+ touchBank: this._touchBank,
+ numberActiveTouches: 0,
+ // If there is only one active touch, we remember its location. This prevents
+ // us having to loop through all of the touches all the time in the most
+ // common case.
+ indexOfSingleActiveTouch: -1,
+ mostRecentTimeStamp: 0
+ };
-const ResponderTouchHistoryStore = {
recordTouchTrack(topLevelType: string, nativeEvent: TouchEvent): void {
if (isMoveish(topLevelType)) {
- nativeEvent.changedTouches.forEach(recordTouchMove);
+ nativeEvent.changedTouches.forEach(this._recordTouchMove);
} else if (isStartish(topLevelType)) {
- nativeEvent.changedTouches.forEach(recordTouchStart);
- touchHistory.numberActiveTouches = nativeEvent.touches.length;
- if (touchHistory.numberActiveTouches === 1) {
- touchHistory.indexOfSingleActiveTouch = nativeEvent.touches[0].identifier;
+ nativeEvent.changedTouches.forEach(this._recordTouchStart);
+ this._touchHistory.numberActiveTouches = nativeEvent.touches.length;
+ if (this._touchHistory.numberActiveTouches === 1) {
+ this._touchHistory.indexOfSingleActiveTouch = nativeEvent.touches[0].identifier;
}
} else if (isEndish(topLevelType)) {
- nativeEvent.changedTouches.forEach(recordTouchEnd);
- touchHistory.numberActiveTouches = nativeEvent.touches.length;
- if (touchHistory.numberActiveTouches === 1) {
- for (let i = 0; i < touchBank.length; i++) {
- const touchTrackToCheck = touchBank[i];
+ nativeEvent.changedTouches.forEach(this._recordTouchEnd);
+ this._touchHistory.numberActiveTouches = nativeEvent.touches.length;
+ if (this._touchHistory.numberActiveTouches === 1) {
+ for (let i = 0; i < this._touchBank.length; i++) {
+ const touchTrackToCheck = this._touchBank[i];
if (touchTrackToCheck != null && touchTrackToCheck.touchActive) {
- touchHistory.indexOfSingleActiveTouch = i;
+ this._touchHistory.indexOfSingleActiveTouch = i;
break;
}
}
if (__DEV__) {
- const activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch];
+ const activeRecord = this._touchBank[this._touchHistory.indexOfSingleActiveTouch];
if (!(activeRecord != null && activeRecord.touchActive)) {
console.error('Cannot find single active touch.');
}
}
}
}
- },
+ }
+
+ get touchHistory() {
+ return this._touchHistory;
+ }
+
+ _recordTouchStart = (touch: Touch): void => {
+ const identifier = getTouchIdentifier(touch);
+ const touchRecord = this._touchBank[identifier];
+ if (touchRecord) {
+ resetTouchRecord(touchRecord, touch);
+ } else {
+ this._touchBank[identifier] = createTouchRecord(touch);
+ }
+ this._touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
+ };
+
+ _recordTouchMove = (touch: Touch): void => {
+ const touchRecord = this._touchBank[getTouchIdentifier(touch)];
+ if (touchRecord) {
+ touchRecord.touchActive = true;
+ touchRecord.previousPageX = touchRecord.currentPageX;
+ touchRecord.previousPageY = touchRecord.currentPageY;
+ touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
+ touchRecord.currentPageX = touch.pageX;
+ touchRecord.currentPageY = touch.pageY;
+ touchRecord.currentTimeStamp = timestampForTouch(touch);
+ this._touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
+ } else {
+ console.warn(
+ 'Cannot record touch move without a touch start.\n',
+ `Touch Move: ${this._printTouch(touch)}\n`,
+ `Touch Bank: ${this._printTouchBank()}`
+ );
+ }
+ };
+
+ _recordTouchEnd = (touch: Touch): void => {
+ const touchRecord = this._touchBank[getTouchIdentifier(touch)];
+ if (touchRecord) {
+ touchRecord.touchActive = false;
+ touchRecord.previousPageX = touchRecord.currentPageX;
+ touchRecord.previousPageY = touchRecord.currentPageY;
+ touchRecord.previousTimeStamp = touchRecord.currentTimeStamp;
+ touchRecord.currentPageX = touch.pageX;
+ touchRecord.currentPageY = touch.pageY;
+ touchRecord.currentTimeStamp = timestampForTouch(touch);
+ this._touchHistory.mostRecentTimeStamp = timestampForTouch(touch);
+ } else {
+ console.warn(
+ 'Cannot record touch end without a touch start.\n',
+ `Touch End: ${this._printTouch(touch)}\n`,
+ `Touch Bank: ${this._printTouchBank()}`
+ );
+ }
+ };
- touchHistory
-};
+ _printTouch(touch: Touch): string {
+ return JSON.stringify({
+ identifier: touch.identifier,
+ pageX: touch.pageX,
+ pageY: touch.pageY,
+ timestamp: timestampForTouch(touch)
+ });
+ }
-export default ResponderTouchHistoryStore;
+ _printTouchBank(): string {
+ let printed = JSON.stringify(this._touchBank.slice(0, MAX_TOUCH_BANK));
+ if (this._touchBank.length > MAX_TOUCH_BANK) {
+ printed += ' (original size: ' + this._touchBank.length + ')';
+ }
+ return printed;
+ }
+}
diff --git a/packages/react-native-web/src/modules/useResponderEvents/__tests__/index-test.js b/packages/react-native-web/src/modules/useResponderEvents/__tests__/index-test.js
index 8dc5106d7a..e9bc7b2f4d 100644
--- a/packages/react-native-web/src/modules/useResponderEvents/__tests__/index-test.js
+++ b/packages/react-native-web/src/modules/useResponderEvents/__tests__/index-test.js
@@ -4,7 +4,8 @@ import { act } from 'react-dom/test-utils';
import React, { createRef } from 'react';
import ReactDOM from 'react-dom';
import useResponderEvents from '..';
-import { getResponderNode, terminateResponder } from '../ResponderSystem';
+import ResponderSystem from '../ResponderSystem';
+import ReactRootView from '../../../exports/AppRegistry/ReactRootView';
import {
buttonType,
buttonsType,
@@ -15,9 +16,15 @@ import {
describe('useResponderEvents', () => {
let container;
+ const responderSystem = new ResponderSystem(window);
function render(element) {
- ReactDOM.render(element, container);
+ ReactDOM.render(
+
+ {element}
+ ,
+ container
+ );
}
beforeEach(() => {
@@ -30,7 +37,7 @@ describe('useResponderEvents', () => {
document.body.removeChild(container);
container = null;
// make sure all tests end with the current responder being reset
- terminateResponder();
+ responderSystem.terminateResponder();
// make sure all tests reset state machine tracking pointers on the mock surface
clearPointers();
});
@@ -54,7 +61,7 @@ describe('useResponderEvents', () => {
target.pointerdown({ pointerType });
});
// no responder should be active
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
test('does nothing for "mousedown" with non-primary buttons', () => {
@@ -83,7 +90,7 @@ describe('useResponderEvents', () => {
});
});
});
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
test('does nothing for "mousedown" with ignored modifier keys', () => {
@@ -109,14 +116,14 @@ describe('useResponderEvents', () => {
target.pointerdown({ pointerType: 'mouse', [modifierKey]: true });
});
});
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
// gesture
act(() => {
acceptedModifierKeys.forEach((modifierKey) => {
target.pointerdown({ pointerType: 'mouse', [modifierKey]: true });
});
});
- expect(getResponderNode()).not.toBe(null);
+ expect(responderSystem.getResponderNode()).not.toBe(null);
});
test('recognizes mouse interactions after touch interactions', () => {
@@ -248,7 +255,7 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onStartShouldSetResponderCapture).not.toBeCalled();
expect(targetCallbacks.onStartShouldSetResponderCapture).not.toBeCalled();
// responder grant
- expect(getResponderNode()).toBe(grandParentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(grandParentRef.current);
expect(grandParentCallbacks.onResponderGrant).toBeCalledTimes(1);
expect(grantCurrentTarget).toBe(grandParentRef.current);
// gesture end
@@ -256,7 +263,7 @@ describe('useResponderEvents', () => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('start grants responder to parent', (pointerType) => {
@@ -298,14 +305,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onStartShouldSetResponderCapture).toBeCalledTimes(1);
expect(targetCallbacks.onStartShouldSetResponderCapture).not.toBeCalled();
// responder grant
- expect(getResponderNode()).toBe(parentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(parentRef.current);
expect(parentCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('start grants responder to child', (pointerType) => {
@@ -347,14 +354,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onStartShouldSetResponderCapture).toBeCalledTimes(1);
expect(targetCallbacks.onStartShouldSetResponderCapture).toBeCalledTimes(1);
// responder grant
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(targetCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
});
@@ -412,14 +419,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onStartShouldSetResponder).not.toBeCalled();
expect(grandParentCallbacks.onStartShouldSetResponder).not.toBeCalled();
// responder grant
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(targetCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('start grants responder to parent', (pointerType) => {
@@ -461,14 +468,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onStartShouldSetResponder).toBeCalledTimes(1);
expect(grandParentCallbacks.onStartShouldSetResponder).not.toBeCalled();
// responder grant
- expect(getResponderNode()).toBe(parentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(parentRef.current);
expect(parentCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('start grants responder to grandParent', (pointerType) => {
@@ -510,14 +517,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onStartShouldSetResponder).toBeCalledTimes(1);
expect(grandParentCallbacks.onStartShouldSetResponder).toBeCalledTimes(1);
// responder grant
- expect(getResponderNode()).toBe(grandParentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(grandParentRef.current);
expect(grandParentCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
});
@@ -576,14 +583,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onMoveShouldSetResponderCapture).not.toBeCalled();
expect(targetCallbacks.onMoveShouldSetResponderCapture).not.toBeCalled();
// responder grant
- expect(getResponderNode()).toBe(grandParentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(grandParentRef.current);
expect(grandParentCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('move grants responder to parent', (pointerType) => {
@@ -626,14 +633,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onMoveShouldSetResponderCapture).toBeCalledTimes(1);
expect(targetCallbacks.onMoveShouldSetResponderCapture).not.toBeCalled();
// responder grant
- expect(getResponderNode()).toBe(parentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(parentRef.current);
expect(parentCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('move grants responder to child', (pointerType) => {
@@ -676,14 +683,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onMoveShouldSetResponderCapture).toBeCalledTimes(1);
expect(targetCallbacks.onMoveShouldSetResponderCapture).toBeCalledTimes(1);
// responder grant
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(targetCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
});
@@ -742,14 +749,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onMoveShouldSetResponder).not.toBeCalled();
expect(grandParentCallbacks.onMoveShouldSetResponder).not.toBeCalled();
// responder grant
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(targetCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('move grants responder to parent', (pointerType) => {
@@ -793,14 +800,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onMoveShouldSetResponder).toBeCalledTimes(1);
expect(grandParentCallbacks.onMoveShouldSetResponder).not.toBeCalled();
// responder grant
- expect(getResponderNode()).toBe(parentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(parentRef.current);
expect(parentCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('move grants responder to grandParent', (pointerType) => {
@@ -843,14 +850,14 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onMoveShouldSetResponder).toBeCalled();
expect(grandParentCallbacks.onMoveShouldSetResponder).toBeCalled();
// responder grant
- expect(getResponderNode()).toBe(grandParentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(grandParentRef.current);
expect(grandParentCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
});
@@ -900,7 +907,7 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onScrollShouldSetResponderCapture).toBeCalledTimes(1);
expect(targetCallbacks.onScrollShouldSetResponderCapture).toBeCalledTimes(0);
// responder grant
- expect(getResponderNode()).toBe(parentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(parentRef.current);
expect(parentCallbacks.onResponderGrant).toBeCalledTimes(1);
});
@@ -937,7 +944,7 @@ describe('useResponderEvents', () => {
expect(parentCallbacks.onScrollShouldSetResponderCapture).toBeCalledTimes(1);
expect(targetCallbacks.onScrollShouldSetResponderCapture).toBeCalledTimes(1);
// responder grant
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(targetCallbacks.onResponderGrant).toBeCalledTimes(1);
});
});
@@ -981,7 +988,7 @@ describe('useResponderEvents', () => {
});
// no bubble
expect(parentCallbacks.onScrollShouldSetResponder).toBeCalledTimes(0);
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('scroll grants responder to target if a pointer is down', (pointerType) => {
@@ -1010,14 +1017,14 @@ describe('useResponderEvents', () => {
// responder set (bubble phase)
expect(targetCallbacks.onScrollShouldSetResponder).toBeCalledTimes(1);
// responder grant
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(targetCallbacks.onResponderGrant).toBeCalledTimes(1);
// gesture end
act(() => {
target.pointerup({ pointerType });
});
// make sure release is called
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
expect(targetCallbacks.onResponderRelease).toBeCalledTimes(1);
});
});
@@ -1293,7 +1300,7 @@ describe('useResponderEvents', () => {
// responder terminates
expect(targetCallbacks.onResponderTerminate).toBeCalledTimes(1);
// responder should not be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
testWithPointerType('is called if "selectionchange" occurs', (pointerType) => {
@@ -1332,7 +1339,7 @@ describe('useResponderEvents', () => {
// responder terminates
expect(targetCallbacks.onResponderTerminate).toBeCalledTimes(1);
// responder should not be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
test('is called if ancestor scrolls', () => {
@@ -1368,7 +1375,7 @@ describe('useResponderEvents', () => {
// responder terminates
expect(targetCallbacks.onResponderTerminate).toBeCalledTimes(1);
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
test('is called if document scrolls', () => {
@@ -1404,7 +1411,7 @@ describe('useResponderEvents', () => {
// responder end
expect(targetCallbacks.onResponderTerminate).toBeCalledTimes(1);
// no responder should be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
test('is not called if sibling scrolls', () => {
@@ -1441,7 +1448,7 @@ describe('useResponderEvents', () => {
// responder doesn't terminate
expect(targetCallbacks.onResponderTerminate).not.toBeCalled();
// responder should still be set
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
});
test('is called if responder blurs', () => {
@@ -1471,7 +1478,7 @@ describe('useResponderEvents', () => {
// responder terminates
expect(targetCallbacks.onResponderTerminate).toBeCalledTimes(1);
// responder should still be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
test('is called if window blurs', () => {
@@ -1501,7 +1508,7 @@ describe('useResponderEvents', () => {
// responder terminates
expect(targetCallbacks.onResponderTerminate).toBeCalledTimes(1);
// responder should not be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
test('is not called if sibling blurs', () => {
@@ -1538,7 +1545,7 @@ describe('useResponderEvents', () => {
// responder doesn't terminate
expect(targetCallbacks.onResponderTerminate).not.toBeCalled();
// responder should still be set
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
});
test('is called if contextmenu opens', () => {
@@ -1568,7 +1575,7 @@ describe('useResponderEvents', () => {
// responder terminates
expect(targetCallbacks.onResponderTerminate).toBeCalledTimes(1);
// responder should not be set
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
test('can be denied for "contextmenu", "scroll", and "selectionchange" events', () => {
@@ -1585,8 +1592,8 @@ describe('useResponderEvents', () => {
function assertAndCleanUp() {
expect(targetCallbacks.onResponderTerminate).toBeCalledTimes(0);
- expect(getResponderNode()).toBe(targetRef.current);
- terminateResponder();
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
+ responderSystem.terminateResponder();
clearPointers();
targetCallbacks.onResponderTerminate.mockClear();
}
@@ -1961,7 +1968,7 @@ describe('useResponderEvents', () => {
target.pointerdown({ pointerType, pointerId: 1 });
});
// target becomes responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture',
@@ -1976,7 +1983,7 @@ describe('useResponderEvents', () => {
target.pointerdown({ pointerType, pointerId: 2 });
});
// target remains responder for start event
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture',
@@ -1990,7 +1997,7 @@ describe('useResponderEvents', () => {
target.pointermove({ pointerType, pointerId: 1 });
});
// parent becomes responder, target terminates
- expect(getResponderNode()).toBe(parentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(parentRef.current);
expect(eventLog).toEqual([
'grandParent: onMoveShouldSetResponderCapture',
'parent: onMoveShouldSetResponderCapture',
@@ -2007,7 +2014,7 @@ describe('useResponderEvents', () => {
target.pointermove({ pointerType, pointerId: 2 });
});
// grand parent becomes responder, parent terminates
- expect(getResponderNode()).toBe(grandParentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(grandParentRef.current);
expect(eventLog).toEqual([
'grandParent: onMoveShouldSetResponderCapture',
'grandParent: onMoveShouldSetResponder',
@@ -2023,7 +2030,7 @@ describe('useResponderEvents', () => {
target.pointerup({ pointerType, pointerId: 2 });
target.pointerup({ pointerType, pointerId: 1 });
});
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
expect(eventLog).toEqual([
'grandParent: onResponderEnd',
'grandParent: onResponderEnd',
@@ -2127,7 +2134,7 @@ describe('useResponderEvents', () => {
parent.pointerdown({ pointerType, pointerId: 1 });
});
// initially nothing wants to become the responder
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture',
@@ -2140,7 +2147,7 @@ describe('useResponderEvents', () => {
target.pointerdown({ pointerType, pointerId: 2 });
});
// target should become responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture',
@@ -2156,7 +2163,7 @@ describe('useResponderEvents', () => {
parent.pointerup({ pointerType, pointerId: 1 });
});
// responder doesn't change, "end" event called on responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual(['target: onResponderEnd']);
eventLog = [];
// add touch back on parent
@@ -2164,7 +2171,7 @@ describe('useResponderEvents', () => {
parent.pointerdown({ pointerType, pointerId: 1 });
});
// responder doesn't change, "start" event called on responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture',
@@ -2178,7 +2185,7 @@ describe('useResponderEvents', () => {
parent.pointermove({ pointerType, pointerId: 1 });
});
// responder doesn't change, "move" event dispatched on responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onMoveShouldSetResponderCapture',
'parent: onMoveShouldSetResponderCapture',
@@ -2192,7 +2199,7 @@ describe('useResponderEvents', () => {
target.pointerup({ pointerType, pointerId: 2 });
});
// responder is released
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
expect(eventLog).toEqual(['target: onResponderEnd', 'target: onResponderRelease']);
});
@@ -2310,7 +2317,7 @@ describe('useResponderEvents', () => {
act(() => {
target.pointerdown({ pointerType, pointerId: 1 });
});
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture',
@@ -2325,7 +2332,7 @@ describe('useResponderEvents', () => {
sibling.pointerdown({ pointerType, pointerId: 2 });
});
// target remains responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
// negotiates from first common ancestor of current responder and sibling (i.e., parent)
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
@@ -2340,7 +2347,7 @@ describe('useResponderEvents', () => {
target.pointermove({ pointerType, pointerId: 1 });
});
// target remains responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
// negotiates from first common ancestor
expect(eventLog).toEqual([
'grandParent: onMoveShouldSetResponderCapture',
@@ -2355,7 +2362,7 @@ describe('useResponderEvents', () => {
sibling.pointermove({ pointerType, pointerId: 2 });
});
// target remains responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
// negotiates from first common ancestor
expect(eventLog).toEqual([
'grandParent: onMoveShouldSetResponderCapture',
@@ -2559,7 +2566,7 @@ describe('useResponderEvents', () => {
target.pointerdown({ pointerType });
});
// target becomes responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture',
@@ -2574,7 +2581,7 @@ describe('useResponderEvents', () => {
target.pointermove({ pointerType });
});
// target remains responder, parent was rejected
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onMoveShouldSetResponderCapture',
'parent: onMoveShouldSetResponderCapture',
@@ -2589,7 +2596,7 @@ describe('useResponderEvents', () => {
target.pointerdown({ pointerType, pointerId: 2 });
});
// target remains responder, parent was rejected
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture',
@@ -2697,7 +2704,7 @@ describe('useResponderEvents', () => {
target.pointerdown({ pointerType });
});
// target becomes responder
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture',
@@ -2712,7 +2719,7 @@ describe('useResponderEvents', () => {
parent.scroll();
});
// target remains responder, parent rejected
- expect(getResponderNode()).toBe(targetRef.current);
+ expect(responderSystem.getResponderNode()).toBe(targetRef.current);
expect(eventLog).toEqual([
'grandParent: onScrollShouldSetResponderCapture',
'parent: onScrollShouldSetResponderCapture',
@@ -2730,7 +2737,7 @@ describe('useResponderEvents', () => {
act(() => {
parent.scroll();
});
- expect(getResponderNode()).toBe(parentRef.current);
+ expect(responderSystem.getResponderNode()).toBe(parentRef.current);
expect(eventLog).toEqual([
'grandParent: onScrollShouldSetResponderCapture',
'parent: onScrollShouldSetResponderCapture',
@@ -2801,7 +2808,7 @@ describe('useResponderEvents', () => {
'grandParent: onStartShouldSetResponderCapture',
'parent: onStartShouldSetResponderCapture'
]);
- expect(getResponderNode()).toBe(null);
+ expect(responderSystem.getResponderNode()).toBe(null);
});
});
});
diff --git a/packages/react-native-web/src/modules/useResponderEvents/createResponderEvent.js b/packages/react-native-web/src/modules/useResponderEvents/createResponderEvent.js
index b7a686bdcf..c8ae4b6d55 100644
--- a/packages/react-native-web/src/modules/useResponderEvents/createResponderEvent.js
+++ b/packages/react-native-web/src/modules/useResponderEvents/createResponderEvent.js
@@ -10,7 +10,7 @@
import type { TouchEvent } from './ResponderEventTypes';
import getBoundingClientRect from '../../modules/getBoundingClientRect';
-import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
+import type ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
export type ResponderEvent = {|
bubbles: boolean,
@@ -70,7 +70,10 @@ function normalizeIdentifier(identifier) {
* Converts a native DOM event to a ResponderEvent.
* Mouse events are transformed into fake touch events.
*/
-export default function createResponderEvent(domEvent: any): ResponderEvent {
+export default function createResponderEvent(
+ domEvent: any,
+ responderTouchHistoryStore: ResponderTouchHistoryStore
+): ResponderEvent {
let rect;
let propagationWasStopped = false;
let changedTouches;
@@ -182,7 +185,7 @@ export default function createResponderEvent(domEvent: any): ResponderEvent {
},
target: domEvent.target,
timeStamp: timestamp,
- touchHistory: ResponderTouchHistoryStore.touchHistory
+ touchHistory: responderTouchHistoryStore.touchHistory
};
// Using getters and functions serves two purposes:
diff --git a/packages/react-native-web/src/modules/useResponderEvents/index.js b/packages/react-native-web/src/modules/useResponderEvents/index.js
index d8f238e65c..72d91ef3a3 100644
--- a/packages/react-native-web/src/modules/useResponderEvents/index.js
+++ b/packages/react-native-web/src/modules/useResponderEvents/index.js
@@ -20,7 +20,7 @@
import type { ResponderConfig } from './ResponderSystem';
import * as React from 'react';
-import * as ResponderSystem from './ResponderSystem';
+import RootContext from '../../exports/AppRegistry/RootContext';
const emptyObject = {};
let idCounter = 0;
@@ -36,16 +36,17 @@ function useStable(getInitialValue: () => T): T {
export default function useResponderEvents(hostRef: any, config: ResponderConfig = emptyObject) {
const id = useStable(() => idCounter++);
const isAttachedRef = React.useRef(false);
+ const rootContext = React.useContext(RootContext);
// This is a separate effects so it doesn't run when the config changes.
// On initial mount, attach global listeners if needed.
// On unmount, remove node potentially attached to the Responder System.
React.useEffect(() => {
- ResponderSystem.attachListeners();
+ rootContext.responderSystem.attachListeners();
return () => {
- ResponderSystem.removeNode(id);
+ rootContext.responderSystem.removeNode(id);
};
- }, [id]);
+ }, [id, rootContext.responderSystem]);
// Register and unregister with the Responder System as necessary
React.useEffect(() => {
@@ -73,14 +74,16 @@ export default function useResponderEvents(hostRef: any, config: ResponderConfig
const node = hostRef.current;
if (requiresResponderSystem) {
- ResponderSystem.addNode(id, node, config);
+ rootContext.responderSystem.addNode(id, node, config);
isAttachedRef.current = true;
} else if (isAttachedRef.current) {
- ResponderSystem.removeNode(id);
+ rootContext.responderSystem.removeNode(id);
isAttachedRef.current = false;
}
- }, [config, hostRef, id]);
+ }, [config, hostRef, id, rootContext.responderSystem]);
- React.useDebugValue({ isResponder: hostRef.current === ResponderSystem.getResponderNode() });
+ React.useDebugValue({
+ isResponder: hostRef.current === rootContext.responderSystem.getResponderNode()
+ });
React.useDebugValue(config);
}