Skip to content

Commit 70ae226

Browse files
committed
importantForLayout property to track the size changes faster.
1 parent 854b41a commit 70ae226

6 files changed

Lines changed: 124 additions & 33 deletions

File tree

src/common/Interfaces.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,6 @@ export abstract class UserInterface {
140140

141141
// On-screen Keyboard
142142
abstract dismissKeyboard(): void;
143-
144-
// Explicit layout change indication
145-
abstract layoutChangePending(): void;
146143
}
147144

148145
export abstract class Modal {

src/common/Types.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ export type LinkStyleRuleSet = StyleRuleSet<LinkStyle>;
277277

278278
export interface ImageStyle extends ViewAndImageCommonStyle, FlexboxStyle {
279279
resizeMode?: 'contain' | 'cover' | 'stretch';
280-
280+
281281
// This is an Android only style attribute that is used to fill the gap in the case of rounded corners
282282
// in gif images.
283283
overlayColor?: string;
@@ -452,7 +452,7 @@ export interface ImagePropsShared extends CommonProps {
452452
children?: ReactNode;
453453
resizeMode?: 'stretch' | 'contain' | 'cover' | 'auto' | 'repeat';
454454

455-
resizeMethod?: 'auto' | 'resize' | 'scale'; // Android only
455+
resizeMethod?: 'auto' | 'resize' | 'scale'; // Android only
456456
title?: string;
457457

458458
onLoad?: (size: Dimensions) => void;
@@ -489,17 +489,17 @@ export interface TextPropsShared extends CommonProps {
489489

490490
// iOS and Android only
491491
ellipsizeMode?: 'head' | 'middle'| 'tail';
492-
492+
493493
// Exposing this property as temporary workaround to fix a bug.
494494
// TODO : http://skype.vso.io/865016 : remove this exposed property
495495
// Used only for Android.
496496
textBreakStrategy?: 'highQuality' | 'simple' | 'balanced';
497497

498498
importantForAccessibility?: ImportantForAccessibility;
499-
499+
500500
// Android only
501501
elevation?: number;
502-
502+
503503
onPress?: (e: SyntheticEvent) => void;
504504
}
505505

@@ -522,6 +522,7 @@ export interface ViewPropsShared extends CommonProps, CommonAccessibilityProps {
522522
viewLayerTypeAndroid?: ViewLayerType; // Android only property
523523
children?: ReactNode;
524524
focusable?: boolean;
525+
importantForLayout?: boolean; // Web-only, additional invisible DOM elements will be added to track the size changes faster
525526

526527
// There are a couple of constraints when child animations are enabled:
527528
// - Every child must have a `key`.
@@ -547,7 +548,7 @@ export interface ViewPropsShared extends CommonProps, CommonAccessibilityProps {
547548
onBlur?: (e: FocusEvent) => void;
548549

549550
// iOS and Android only. Visual touchfeedback properties
550-
disableTouchOpacityAnimation?: boolean;
551+
disableTouchOpacityAnimation?: boolean;
551552
activeOpacity?: number;
552553
underlayColor?: string;
553554
}
@@ -762,7 +763,7 @@ export interface TextInputPropsShared extends CommonProps, CommonAccessibilityPr
762763
secureTextEntry?: boolean;
763764
value?: string;
764765
textAlign?: 'auto' | 'left' | 'right' | 'center' | 'justify';
765-
766+
766767
// Should fonts be scaled according to system setting? Defaults
767768
// to true. iOS and Android only.
768769
allowFontScaling?: boolean;
@@ -869,9 +870,9 @@ export interface PopupOptions {
869870
popupWidth: number, popupHeight: number) => ReactNode;
870871

871872
// Returns a mounted component instance that controls the triggering of the popup.
872-
// In majority of cases, "anchor" of popup has handlers to control when the popup will be seen and this function is not required.
873+
// In majority of cases, "anchor" of popup has handlers to control when the popup will be seen and this function is not required.
873874
// In a few cases, where anchor is not the same as the whole component that triggers when the popup wil be seen, this can be used.
874-
// For instance, a button combined with a chevron icon, which on click triggers a popup below the chevron icon.
875+
// For instance, a button combined with a chevron icon, which on click triggers a popup below the chevron icon.
875876
// In this example, getElementTriggeringPopup() can return the container with button and chevron icon.
876877
getElementTriggeringPopup?: () => React.Component<any, any>;
877878

@@ -893,8 +894,8 @@ export interface PopupOptions {
893894
// already unmounted as it uses a time delay to accommodate a fade-out animation.
894895
onAnchorPressed?: (e: RX.Types.SyntheticEvent) => void;
895896

896-
// Determines if the anchor invoking the popup should behave like a toggle.
897-
// Value = true => Calling Popup.show will show the popup. A subsequent call, will hide the popup, and so on.
897+
// Determines if the anchor invoking the popup should behave like a toggle.
898+
// Value = true => Calling Popup.show will show the popup. A subsequent call, will hide the popup, and so on.
898899
// Value = false or undefined (default) => Calling Popup.show will always show the popup.
899900
dismissIfShown?: boolean;
900901
}

src/native-common/UserInterface.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,6 @@ export class UserInterface extends RX.UserInterface {
121121
renderMainView() {
122122
// Nothing to do
123123
}
124-
125-
layoutChangePending() {
126-
// Nothing to do
127-
}
128124
}
129125

130126
export default new UserInterface();

src/web/UserInterface.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,6 @@ export class UserInterface extends RX.UserInterface {
107107
dismissKeyboard() {
108108
// Nothing to do
109109
}
110-
111-
layoutChangePending() {
112-
if (!this._layoutChangeAnimationFrame) {
113-
// ViewBase checks for the layout changes once a second or on window resize.
114-
// To avoid laggy layout updates we can indicate that there is a change pending.
115-
this._layoutChangeAnimationFrame = window.requestAnimationFrame(() => {
116-
this._layoutChangeAnimationFrame = undefined;
117-
let event = document.createEvent('HTMLEvents');
118-
event.initEvent('resize', true, false);
119-
window.dispatchEvent(event);
120-
});
121-
}
122-
}
123110
}
124111

125112
export default new UserInterface();

src/web/View.tsx

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import React = require('react');
11+
import ReactDOM = require('react-dom');
1112

1213
import AccessibilityUtil from './AccessibilityUtil';
1314
import AnimateListEdits from './listAnimations/AnimateListEdits';
@@ -24,6 +25,31 @@ const _styles = {
2425
flex: '0 0 auto',
2526
overflow: 'hidden',
2627
alignItems: 'stretch'
28+
},
29+
30+
resizeDetectorContainerStyles: {
31+
position: 'absolute',
32+
left: '0',
33+
top: '0',
34+
right: '0',
35+
bottom: '0',
36+
overflow: 'scroll',
37+
zIndex: '-1',
38+
visibility: 'hidden'
39+
},
40+
41+
resizeGrowDetectorStyles: {
42+
position: 'absolute',
43+
left: '100500px',
44+
top: '100500px',
45+
width: '1px',
46+
height: '1px'
47+
},
48+
49+
resizeShrinkDetectorStyles: {
50+
position: 'absolute',
51+
width: '150%',
52+
height: '150%'
2753
}
2854
};
2955

@@ -50,6 +76,89 @@ export class View extends ViewBase<Types.ViewProps, {}> {
5076
isRxParentAText: React.PropTypes.bool.isRequired
5177
};
5278

79+
private resizeDetectorAnimationFrame: number;
80+
private resizeDetectorNodes: {grow?: HTMLElement, shrink?: HTMLElement} = {};
81+
82+
private resizeDetectorReset() {
83+
const scrollMax = 100500;
84+
85+
let node = this.resizeDetectorNodes.grow;
86+
87+
if (node) {
88+
node.scrollLeft = scrollMax;
89+
node.scrollTop = scrollMax;
90+
}
91+
92+
node = this.resizeDetectorNodes.shrink;
93+
94+
if (node) {
95+
node.scrollLeft = scrollMax;
96+
node.scrollTop = scrollMax;
97+
}
98+
}
99+
100+
private resizeDetectorOnScroll() {
101+
if (this.resizeDetectorAnimationFrame) {
102+
return;
103+
}
104+
105+
this.resizeDetectorAnimationFrame = window.requestAnimationFrame(() => {
106+
this.resizeDetectorReset();
107+
this.resizeDetectorAnimationFrame = undefined;
108+
ViewBase._checkViews();
109+
});
110+
111+
}
112+
113+
private renderResizeDetectorIfNeeded(containerStyles: any): React.ReactNode {
114+
if (!this.props.importantForLayout) {
115+
return null;
116+
}
117+
118+
if (containerStyles.position !== 'relative') {
119+
console.error('View: importantForLayout property is applicable only for a view with relative position');
120+
return null;
121+
}
122+
123+
let initResizer = (key: 'grow' | 'shrink', ref: React.DOMComponent<React.HTMLAttributes>) => {
124+
const cur: HTMLElement = this.resizeDetectorNodes[key];
125+
const element = ReactDOM.findDOMNode<HTMLElement>(ref);
126+
127+
if (cur) {
128+
delete this.resizeDetectorNodes[key];
129+
}
130+
131+
if (element) {
132+
this.resizeDetectorNodes[key] = element;
133+
}
134+
135+
this.resizeDetectorOnScroll();
136+
};
137+
138+
return [
139+
(
140+
<div
141+
key='grow'
142+
style={ _styles.resizeDetectorContainerStyles }
143+
ref={ (ref) => initResizer('grow', ref) }
144+
onScroll={ () => this.resizeDetectorOnScroll() }>
145+
146+
<div style={_styles.resizeGrowDetectorStyles}></div>
147+
</div>
148+
),
149+
(
150+
<div
151+
key='shrink'
152+
style={ _styles.resizeDetectorContainerStyles }
153+
ref={ (ref) => initResizer('shrink', ref) }
154+
onScroll={ () => this.resizeDetectorOnScroll() }>
155+
156+
<div style={_styles.resizeShrinkDetectorStyles}></div>
157+
</div>
158+
)
159+
];
160+
}
161+
53162
getChildContext() {
54163
// Let descendant Types components know that their nearest Types ancestor is not an Types.Text.
55164
// Because they're in an Types.View, they should use their normal styling rather than their
@@ -66,7 +175,7 @@ export class View extends ViewBase<Types.ViewProps, {}> {
66175
const ariaRole = AccessibilityUtil.accessibilityTraitToString(this.props.accessibilityTraits);
67176
const ariaSelected = AccessibilityUtil.accessibilityTraitToAriaSelected(this.props.accessibilityTraits);
68177
const isAriaHidden = AccessibilityUtil.isHidden(this.props.importantForAccessibility);
69-
178+
70179
let props: Types.AccessibilityHtmlAttributes = {
71180
role: ariaRole,
72181
tabIndex: this.props.tabIndex,
@@ -111,6 +220,7 @@ export class View extends ViewBase<Types.ViewProps, {}> {
111220
} else {
112221
reactElement = (
113222
<div {...props} >
223+
{this.renderResizeDetectorIfNeeded(combinedStyles)}
114224
{this.props.children}
115225
</div>
116226
);

src/web/ViewBase.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export abstract class ViewBase<P extends Types.ViewProps, S> extends RX.ViewBase
7878
}
7979
}
8080

81-
private static _checkViews() {
81+
protected static _checkViews() {
8282
_.each(ViewBase._viewCheckingList, view => {
8383
view._checkAndReportLayout();
8484
});

0 commit comments

Comments
 (0)