From 4b34a77d290cf67ab0be51bf8edef0fe9787732b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 20 Jul 2019 14:08:23 -0700 Subject: [PATCH] Improve Bridge Flow types (#352) * Updated local fork of react-window * Updated Fow 97 -> 103 * Lint ignore NPM dist * Improved Bridge Flow types --- .eslintignore | 1 + package.json | 4 +- packages/react-devtools-core/src/backend.js | 3 +- .../react-devtools-core/src/standalone.js | 7 +- shells/dev/app/SuspenseTree/index.js | 40 +-- src/__tests__/bridge-test.js | 10 +- src/__tests__/inspectedElementContext-test.js | 4 +- src/__tests__/legacy/inspectElement-test.js | 4 +- src/__tests__/ownersListContext-test.js | 4 +- src/__tests__/profilerContext-test.js | 4 +- src/__tests__/profilingCache-test.js | 4 +- src/__tests__/profilingCharts-test.js | 6 +- src/__tests__/setupTests.js | 6 +- src/__tests__/storeComponentFilters-test.js | 4 +- src/__tests__/treeContext-test.js | 4 +- src/__tests__/utils.js | 4 +- .../setupNativeStyleEditor.js | 6 +- src/backend/agent.js | 8 +- src/backend/views/Highlighter/Overlay.js | 13 +- src/backend/views/Highlighter/index.js | 10 +- src/bridge.js | 75 +++-- src/devtools/ProfilerStore.js | 10 +- src/devtools/index.js | 4 +- src/devtools/store.js | 8 +- src/devtools/views/Components/HooksTree.js | 24 +- .../Components/InspectedElementContext.js | 12 +- .../NativeStyleEditor/StyleEditor.js | 32 +- .../Components/NativeStyleEditor/context.js | 10 +- .../views/Components/OwnersListContext.js | 5 +- .../views/Components/SelectedElement.js | 40 ++- src/devtools/views/Components/Tree.js | 2 +- src/devtools/views/DevTools.js | 4 +- src/devtools/views/context.js | 8 +- vendor/react-window/dist/index-dev.umd.js | 2 + vendor/react-window/dist/index-prod.umd.js | 2 + vendor/react-window/dist/index.cjs.js | 309 +++++++++++++---- vendor/react-window/dist/index.esm.js | 317 ++++++++++++++---- vendor/react-window/package.json | 8 +- vendor/react-window/src/FixedSizeGrid.js | 76 +++-- vendor/react-window/src/FixedSizeList.js | 41 ++- vendor/react-window/src/VariableSizeGrid.js | 6 +- vendor/react-window/src/VariableSizeList.js | 2 +- .../react-window/src/createGridComponent.js | 132 ++++++-- .../react-window/src/createListComponent.js | 94 ++++-- vendor/react-window/src/domHelpers.js | 50 +++ yarn.lock | 26 +- 46 files changed, 1066 insertions(+), 379 deletions(-) create mode 100644 vendor/react-window/dist/index-dev.umd.js create mode 100644 vendor/react-window/dist/index-prod.umd.js diff --git a/.eslintignore b/.eslintignore index 564cfdfb14d2..5e4165322a11 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,6 +4,7 @@ shells/browser/chrome/build shells/browser/firefox/build shells/browser/shared/build shells/dev/dist +packages/react-devtools-core/dist vendor *.js.snap diff --git a/package.json b/package.json index 18c5527081a3..f5dd64b0dee1 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "test:chrome": "node ./shells/browser/chrome/test", "test:firefox": "node ./shells/browser/firefox/test", "test:standalone": "cd packages/react-devtools && yarn start", - "typecheck": "flow check" + "typecheck": "flow check --show-all-errors" }, "devEngines": { "node": "10.x || 11.x" @@ -119,7 +119,7 @@ "fbjs": "0.5.1", "fbjs-scripts": "0.7.0", "firefox-profile": "^1.0.2", - "flow-bin": "^0.97.0", + "flow-bin": "^0.103.0", "fs-extra": "^3.0.1", "gh-pages": "^1.0.0", "html2canvas": "^1.0.0-alpha.12", diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js index f76659180aa5..11a5dde7c96c 100644 --- a/packages/react-devtools-core/src/backend.js +++ b/packages/react-devtools-core/src/backend.js @@ -8,6 +8,7 @@ import { __DEBUG__ } from 'src/constants'; import setupNativeStyleEditor from 'src/backend/NativeStyleEditor/setupNativeStyleEditor'; import { getDefaultComponentFilters } from 'src/utils'; +import type { BackendBridge } from 'src/bridge'; import type { ComponentFilter } from 'src/types'; import type { DevToolsHook } from 'src/backend/types'; import type { ResolveNativeStyle } from 'src/backend/NativeStyleEditor/setupNativeStyleEditor'; @@ -64,7 +65,7 @@ export function connectToDevTools(options: ?ConnectOptions) { return; } - let bridge: Bridge | null = null; + let bridge: BackendBridge | null = null; const messageListeners = []; const uri = 'ws://' + host + ':' + port; diff --git a/packages/react-devtools-core/src/standalone.js b/packages/react-devtools-core/src/standalone.js index 7bd636b80e44..b9fc4ff25a80 100644 --- a/packages/react-devtools-core/src/standalone.js +++ b/packages/react-devtools-core/src/standalone.js @@ -17,6 +17,7 @@ import DevTools from 'src/devtools/views/DevTools'; import launchEditor from './launchEditor'; import { __DEBUG__ } from 'src/constants'; +import type { FrontendBridge } from 'src/bridge'; import type { InspectedElement } from 'src/devtools/views/Components/types'; installHook(window); @@ -46,7 +47,7 @@ function setStatusListener(value: StatusListener) { return DevtoolsUI; } -let bridge: Bridge | null = null; +let bridge: FrontendBridge | null = null; let store: Store | null = null; let root = null; @@ -83,7 +84,7 @@ function reload() { root = createRoot(node); root.render( createElement(DevTools, { - bridge: ((bridge: any): Bridge), + bridge: ((bridge: any): FrontendBridge), showTabBar: true, store: ((store: any): Store), warnIfLegacyBackendDetected: true, @@ -166,7 +167,7 @@ function initialize(socket: WebSocket) { } }, }); - ((bridge: any): Bridge).addListener('shutdown', () => { + ((bridge: any): FrontendBridge).addListener('shutdown', () => { socket.close(); }); diff --git a/shells/dev/app/SuspenseTree/index.js b/shells/dev/app/SuspenseTree/index.js index e9a1fef4d2b5..873f210e163d 100644 --- a/shells/dev/app/SuspenseTree/index.js +++ b/shells/dev/app/SuspenseTree/index.js @@ -1,17 +1,17 @@ // @flow -import React, { Suspense, useState } from 'react'; +import React, { Fragment, Suspense, useState } from 'react'; function SuspenseTree() { return ( - <> +

Suspense

Primary to Fallback Cycle

Fallback to Primary Cycle

- +
); } @@ -20,7 +20,7 @@ function PrimaryFallbackTest({ initialSuspend }) { const fallbackStep = useTestSequence('fallback', Fallback1, Fallback2); const primaryStep = useTestSequence('primary', Primary1, Primary2); return ( - <> + ); } @@ -45,32 +45,32 @@ function useTestSequence(label, T1, T2) { ); let allSteps = [ - <>{next}, - <> + {next}, + {next} mount - , - <> + , + {next} update - , - <> + , + {next} several different{' '} children - , - <> + , + {next} goodbye - , + , ]; return allSteps[step]; } function NestedSuspenseTest() { return ( - <> +

Nested Suspense

Loading outer}> - +
); } @@ -118,19 +118,19 @@ function Never() { throw new Promise(resolve => {}); } -function Fallback1({ prop, ...rest }) { +function Fallback1({ prop, ...rest }: any) { return ; } -function Fallback2({ prop, ...rest }) { +function Fallback2({ prop, ...rest }: any) { return ; } -function Primary1({ prop, ...rest }) { +function Primary1({ prop, ...rest }: any) { return ; } -function Primary2({ prop, ...rest }) { +function Primary2({ prop, ...rest }: any) { return ; } diff --git a/src/__tests__/bridge-test.js b/src/__tests__/bridge-test.js index 1a9b4b949a5c..fe89a6c2b10f 100644 --- a/src/__tests__/bridge-test.js +++ b/src/__tests__/bridge-test.js @@ -15,9 +15,9 @@ describe('Bridge', () => { const bridge = new Bridge(wall); // Check that we're wired up correctly. - bridge.send('init'); + bridge.send('reloadAppForProfiling'); jest.runAllTimers(); - expect(wall.send).toHaveBeenCalledWith('init', undefined, undefined); + expect(wall.send).toHaveBeenCalledWith('reloadAppForProfiling'); // Should flush pending messages and then shut down. wall.send.mockClear(); @@ -25,9 +25,9 @@ describe('Bridge', () => { bridge.send('update', '2'); bridge.shutdown(); jest.runAllTimers(); - expect(wall.send).toHaveBeenCalledWith('update', '1', undefined); - expect(wall.send).toHaveBeenCalledWith('update', '2', undefined); - expect(wall.send).toHaveBeenCalledWith('shutdown', undefined, undefined); + expect(wall.send).toHaveBeenCalledWith('update', '1'); + expect(wall.send).toHaveBeenCalledWith('update', '2'); + expect(wall.send).toHaveBeenCalledWith('shutdown'); // Verify that the Bridge doesn't send messages after shutdown. spyOn(console, 'warn'); diff --git a/src/__tests__/inspectedElementContext-test.js b/src/__tests__/inspectedElementContext-test.js index 2fdbfe95e890..2cf1ed863297 100644 --- a/src/__tests__/inspectedElementContext-test.js +++ b/src/__tests__/inspectedElementContext-test.js @@ -2,14 +2,14 @@ import typeof ReactTestRenderer from 'react-test-renderer'; import type { GetInspectedElementPath } from 'src/devtools/views/Components/InspectedElementContext'; -import type Bridge from 'src/bridge'; +import type { FrontendBridge } from 'src/bridge'; import type Store from 'src/devtools/store'; describe('InspectedElementContext', () => { let React; let ReactDOM; let TestRenderer: ReactTestRenderer; - let bridge: Bridge; + let bridge: FrontendBridge; let store: Store; let meta; let utils; diff --git a/src/__tests__/legacy/inspectElement-test.js b/src/__tests__/legacy/inspectElement-test.js index f71d22343f15..7b8c2b04a9f5 100644 --- a/src/__tests__/legacy/inspectElement-test.js +++ b/src/__tests__/legacy/inspectElement-test.js @@ -2,7 +2,7 @@ import type { InspectedElementPayload } from 'src/backend/types'; import type { DehydratedData } from 'src/devtools/views/Components/types'; -import type Bridge from 'src/bridge'; +import type { FrontendBridge } from 'src/bridge'; import type Store from 'src/devtools/store'; describe('InspectedElementContext', () => { @@ -10,7 +10,7 @@ describe('InspectedElementContext', () => { let ReactDOM; let hydrate; let meta; - let bridge: Bridge; + let bridge: FrontendBridge; let store: Store; const act = (callback: Function) => { diff --git a/src/__tests__/ownersListContext-test.js b/src/__tests__/ownersListContext-test.js index 7dde191cf997..97681b70bdde 100644 --- a/src/__tests__/ownersListContext-test.js +++ b/src/__tests__/ownersListContext-test.js @@ -2,14 +2,14 @@ import typeof ReactTestRenderer from 'react-test-renderer'; import type { Element } from 'src/devtools/views/Components/types'; -import type Bridge from 'src/bridge'; +import type { FrontendBridge } from 'src/bridge'; import type Store from 'src/devtools/store'; describe('OwnersListContext', () => { let React; let ReactDOM; let TestRenderer: ReactTestRenderer; - let bridge: Bridge; + let bridge: FrontendBridge; let store: Store; let utils; diff --git a/src/__tests__/profilerContext-test.js b/src/__tests__/profilerContext-test.js index 6857ddb4070c..c06f70d0390d 100644 --- a/src/__tests__/profilerContext-test.js +++ b/src/__tests__/profilerContext-test.js @@ -1,7 +1,7 @@ // @flow import typeof ReactTestRenderer from 'react-test-renderer'; -import type Bridge from 'src/bridge'; +import type { FrontendBridge } from 'src/bridge'; import type { Context } from 'src/devtools/views/Profiler/ProfilerContext'; import type { DispatcherContext } from 'src/devtools/views/Components/TreeContext'; import type Store from 'src/devtools/store'; @@ -10,7 +10,7 @@ describe('ProfilerContext', () => { let React; let ReactDOM; let TestRenderer: ReactTestRenderer; - let bridge: Bridge; + let bridge: FrontendBridge; let store: Store; let utils; diff --git a/src/__tests__/profilingCache-test.js b/src/__tests__/profilingCache-test.js index e5b56028936f..6f6e4742cda4 100644 --- a/src/__tests__/profilingCache-test.js +++ b/src/__tests__/profilingCache-test.js @@ -1,7 +1,7 @@ // @flow import typeof ReactTestRenderer from 'react-test-renderer'; -import type Bridge from 'src/bridge'; +import type { FrontendBridge } from 'src/bridge'; import type Store from 'src/devtools/store'; describe('ProfilingCache', () => { @@ -11,7 +11,7 @@ describe('ProfilingCache', () => { let Scheduler; let SchedulerTracing; let TestRenderer: ReactTestRenderer; - let bridge: Bridge; + let bridge: FrontendBridge; let store: Store; let utils; diff --git a/src/__tests__/profilingCharts-test.js b/src/__tests__/profilingCharts-test.js index dbf1ee16544b..0e7fff951e94 100644 --- a/src/__tests__/profilingCharts-test.js +++ b/src/__tests__/profilingCharts-test.js @@ -29,7 +29,7 @@ describe('profiling charts', () => { describe('flamegraph chart', () => { it('should contain valid data', () => { - const Parent = ({ count }) => { + const Parent = (_: {||}) => { Scheduler.unstable_advanceTime(10); return ( @@ -105,7 +105,7 @@ describe('profiling charts', () => { describe('ranked chart', () => { it('should contain valid data', () => { - const Parent = ({ count }) => { + const Parent = (_: {||}) => { Scheduler.unstable_advanceTime(10); return ( @@ -177,7 +177,7 @@ describe('profiling charts', () => { describe('interactions', () => { it('should contain valid data', () => { - const Parent = ({ count }) => { + const Parent = (_: {||}) => { Scheduler.unstable_advanceTime(10); return ( diff --git a/src/__tests__/setupTests.js b/src/__tests__/setupTests.js index 18127c4d47ad..3fb0a57a2f27 100644 --- a/src/__tests__/setupTests.js +++ b/src/__tests__/setupTests.js @@ -1,5 +1,7 @@ // @flow +import type { BackendBridge, FrontendBridge } from 'src/bridge'; + const env = jasmine.getEnv(); env.beforeEach(() => { // These files should be required (and re-reuired) before each test, @@ -51,13 +53,13 @@ env.beforeEach(() => { }, }); - const agent = new Agent(bridge); + const agent = new Agent(((bridge: any): BackendBridge)); const hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__; initBackend(hook, agent, global); - const store = new Store(bridge); + const store = new Store(((bridge: any): FrontendBridge)); global.agent = agent; global.bridge = bridge; diff --git a/src/__tests__/storeComponentFilters-test.js b/src/__tests__/storeComponentFilters-test.js index 3b7cfa53010d..5e2385b33b96 100644 --- a/src/__tests__/storeComponentFilters-test.js +++ b/src/__tests__/storeComponentFilters-test.js @@ -1,6 +1,6 @@ // @flow -import type Bridge from 'src/bridge'; +import type { FrontendBridge } from 'src/bridge'; import type Store from 'src/devtools/store'; describe('Store component filters', () => { @@ -8,7 +8,7 @@ describe('Store component filters', () => { let ReactDOM; let TestUtils; let Types; - let bridge: Bridge; + let bridge: FrontendBridge; let store: Store; let utils; diff --git a/src/__tests__/treeContext-test.js b/src/__tests__/treeContext-test.js index 09940efe95dd..ecf8d4771743 100644 --- a/src/__tests__/treeContext-test.js +++ b/src/__tests__/treeContext-test.js @@ -1,7 +1,7 @@ // @flow import typeof ReactTestRenderer from 'react-test-renderer'; -import type Bridge from 'src/bridge'; +import type { FrontendBridge } from 'src/bridge'; import type Store from 'src/devtools/store'; import type { DispatcherContext, @@ -12,7 +12,7 @@ describe('TreeListContext', () => { let React; let ReactDOM; let TestRenderer: ReactTestRenderer; - let bridge: Bridge; + let bridge: FrontendBridge; let store: Store; let utils; diff --git a/src/__tests__/utils.js b/src/__tests__/utils.js index 345b773832aa..bf0c816109fa 100644 --- a/src/__tests__/utils.js +++ b/src/__tests__/utils.js @@ -2,7 +2,7 @@ import typeof ReactTestRenderer from 'react-test-renderer'; -import type Bridge from 'src/bridge'; +import type { FrontendBridge } from 'src/bridge'; import type Store from 'src/devtools/store'; import type { ProfilingDataFrontend } from 'src/devtools/views/Profiler/types'; import type { ElementType } from 'src/types'; @@ -161,7 +161,7 @@ export function requireTestRenderer(): ReactTestRenderer { } } -export function exportImportHelper(bridge: Bridge, store: Store): void { +export function exportImportHelper(bridge: FrontendBridge, store: Store): void { const { act } = require('./utils'); const { prepareProfilingDataExport, diff --git a/src/backend/NativeStyleEditor/setupNativeStyleEditor.js b/src/backend/NativeStyleEditor/setupNativeStyleEditor.js index 5880bbac4d40..642dbb91eb87 100644 --- a/src/backend/NativeStyleEditor/setupNativeStyleEditor.js +++ b/src/backend/NativeStyleEditor/setupNativeStyleEditor.js @@ -1,16 +1,16 @@ // @flow import Agent from 'src/backend/agent'; -import Bridge from 'src/bridge'; import resolveBoxStyle from './resolveBoxStyle'; +import type { BackendBridge } from 'src/bridge'; import type { RendererID } from '../types'; import type { StyleAndLayout } from './types'; export type ResolveNativeStyle = (stylesheetID: number) => ?Object; export default function setupNativeStyleEditor( - bridge: Bridge, + bridge: BackendBridge, agent: Agent, resolveNativeStyle: ResolveNativeStyle, validAttributes?: $ReadOnlyArray | null @@ -81,7 +81,7 @@ const componentIDToStyleOverrides: Map = new Map(); function measureStyle( agent: Agent, - bridge: Bridge, + bridge: BackendBridge, resolveNativeStyle: ResolveNativeStyle, id: number, rendererID: RendererID diff --git a/src/backend/agent.js b/src/backend/agent.js index fadd2e697ee5..9d8c4cff3710 100644 --- a/src/backend/agent.js +++ b/src/backend/agent.js @@ -2,7 +2,6 @@ import EventEmitter from 'events'; import throttle from 'lodash.throttle'; -import Bridge from 'src/bridge'; import { SESSION_STORAGE_LAST_SELECTION_KEY, SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, @@ -17,6 +16,7 @@ import { import setupHighlighter from './views/Highlighter'; import { patch as patchConsole, unpatch as unpatchConsole } from './console'; +import type { BackendBridge } from 'src/bridge'; import type { InstanceAndStyle, NativeType, @@ -81,14 +81,14 @@ export default class Agent extends EventEmitter<{| showNativeHighlight: [NativeType], shutdown: [], |}> { - _bridge: Bridge; + _bridge: BackendBridge; _isProfiling: boolean = false; _recordChangeDescriptions: boolean = false; _rendererInterfaces: { [key: RendererID]: RendererInterface } = {}; _persistedSelection: PersistedSelection | null = null; _persistedSelectionMatch: PathMatch | null = null; - constructor(bridge: Bridge) { + constructor(bridge: BackendBridge) { super(); if ( @@ -259,7 +259,7 @@ export default class Agent extends EventEmitter<{| dataURL: string, rootID: number, |}) => { - this._bridge.send('screenshotCaptured', { commitIndex, dataURL }); + this._bridge.send('screenshotCaptured', { commitIndex, dataURL, rootID }); }; selectElement = ({ id, rendererID }: ElementAndRendererID) => { diff --git a/src/backend/views/Highlighter/Overlay.js b/src/backend/views/Highlighter/Overlay.js index 55ef361dc627..f2190066b479 100644 --- a/src/backend/views/Highlighter/Overlay.js +++ b/src/backend/views/Highlighter/Overlay.js @@ -11,6 +11,8 @@ type Rect = { width: number, }; +type Box = {| top: number, left: number, width: number, height: number |}; + // Note that the Overlay components are not affected by the active Theme, // because they highlight elements in the main Chrome window (outside of devtools). // The colors below were chosen to roughly match those used by Chrome devtools. @@ -21,7 +23,7 @@ class OverlayRect { padding: HTMLElement; content: HTMLElement; - constructor(doc, container) { + constructor(doc: Document, container: HTMLElement) { this.node = doc.createElement('div'); this.border = doc.createElement('div'); this.padding = doc.createElement('div'); @@ -51,7 +53,7 @@ class OverlayRect { } } - update(box, dims) { + update(box: Rect, dims: any) { boxWrap(dims, 'margin', this.node); boxWrap(dims, 'border', this.border); boxWrap(dims, 'padding', this.padding); @@ -85,7 +87,7 @@ class OverlayTip { nameSpan: HTMLElement; dimSpan: HTMLElement; - constructor(doc, container) { + constructor(doc: Document, container: HTMLElement) { this.tip = doc.createElement('div'); assign(this.tip.style, { display: 'flex', @@ -126,13 +128,13 @@ class OverlayTip { } } - updateText(name, width, height) { + updateText(name: string, width: number, height: number) { this.nameSpan.textContent = name; this.dimSpan.textContent = Math.round(width) + 'px × ' + Math.round(height) + 'px'; } - updatePosition(dims, bounds) { + updatePosition(dims: Box, bounds: Box) { const tipRect = this.tip.getBoundingClientRect(); const tipPos = findTipPos(dims, bounds, { width: tipRect.width, @@ -247,6 +249,7 @@ export default class Overlay { this.tipBoundsWindow.document.documentElement, this.window ); + this.tip.updatePosition( { top: outerBox.top, diff --git a/src/backend/views/Highlighter/index.js b/src/backend/views/Highlighter/index.js index 3096b6fab3eb..70d64e4cbc05 100644 --- a/src/backend/views/Highlighter/index.js +++ b/src/backend/views/Highlighter/index.js @@ -2,11 +2,15 @@ import memoize from 'memoize-one'; import throttle from 'lodash.throttle'; -import Bridge from 'src/bridge'; import Agent from 'src/backend/agent'; import { hideOverlay, showOverlay } from './Highlighter'; -export default function setup(bridge: Bridge, agent: Agent): void { +import type { BackendBridge } from 'src/bridge'; + +export default function setupHighlighter( + bridge: BackendBridge, + agent: Agent +): void { bridge.addListener( 'clearNativeElementHighlight', clearNativeElementHighlight @@ -50,7 +54,7 @@ export default function setup(bridge: Bridge, agent: Agent): void { rendererID, scrollIntoView, }: { - displayName: string, + displayName: string | null, hideAfterTimeout: boolean, id: number, openNativeElementsPanel: boolean, diff --git a/src/bridge.js b/src/bridge.js index 23b887af7fc7..b8ab73e710e9 100644 --- a/src/bridge.js +++ b/src/bridge.js @@ -22,7 +22,7 @@ type Message = {| type HighlightElementInDOM = {| ...ElementAndRendererID, - displayName: string, + displayName: string | null, hideAfterTimeout: boolean, openNativeElementsPanel: boolean, scrollIntoView: boolean, @@ -62,33 +62,48 @@ type NativeStyleEditor_SetValueParams = {| value: string, |}; -export default class Bridge extends EventEmitter<{| +type BackendEvents = {| + captureScreenshot: [{| commitIndex: number, rootID: number |}], + inspectedElement: [InspectedElementPayload], + isBackendStorageAPISupported: [boolean], + operations: [Array], + ownersList: [OwnersList], + overrideComponentFilters: [Array], + profilingData: [ProfilingDataBackend], + profilingStatus: [boolean], + reloadAppForProfiling: [], + screenshotCaptured: [ + {| commitIndex: number, dataURL: string, rootID: number |}, + ], + selectFiber: [number], + shutdown: [], + stopInspectingNative: [boolean], + syncSelectionFromNativeElementsPanel: [], + syncSelectionToNativeElementsPanel: [], + + // React Native style editor plug-in. + isNativeStyleEditorSupported: [ + {| isSupported: boolean, validAttributes: ?$ReadOnlyArray |}, + ], + NativeStyleEditor_styleAndLayout: [StyleAndLayoutPayload], +|}; + +type FrontendEvents = {| captureScreenshot: [{| commitIndex: number, rootID: number |}], clearNativeElementHighlight: [], getOwnersList: [ElementAndRendererID], getProfilingData: [{| rendererID: RendererID |}], getProfilingStatus: [], highlightNativeElement: [HighlightElementInDOM], - init: [], inspectElement: [InspectElementParams], - inspectedElement: [InspectedElementPayload], - isBackendStorageAPISupported: [boolean], logElementToConsole: [ElementAndRendererID], - operations: [Array], - ownersList: [OwnersList], - overrideComponentFilters: [Array], overrideContext: [OverrideValue], overrideHookState: [OverrideHookState], overrideProps: [OverrideValue], overrideState: [OverrideValue], overrideSuspense: [OverrideSuspense], profilingData: [ProfilingDataBackend], - profilingStatus: [boolean], reloadAndProfile: [boolean], - reloadAppForProfiling: [], - screenshotCaptured: [ - {| commitIndex: number, dataURL: string, rootID: number |}, - ], selectElement: [ElementAndRendererID], selectFiber: [number], shutdown: [], @@ -96,20 +111,22 @@ export default class Bridge extends EventEmitter<{| startProfiling: [boolean], stopInspectingNative: [boolean], stopProfiling: [], - syncSelectionFromNativeElementsPanel: [], - syncSelectionToNativeElementsPanel: [], updateAppendComponentStack: [boolean], updateComponentFilters: [Array], viewElementSource: [ElementAndRendererID], // React Native style editor plug-in. - isNativeStyleEditorSupported: [ - {| isSupported: boolean, validAttributes: $ReadOnlyArray |}, - ], NativeStyleEditor_measure: [ElementAndRendererID], NativeStyleEditor_renameAttribute: [NativeStyleEditor_RenameAttributeParams], NativeStyleEditor_setValue: [NativeStyleEditor_SetValueParams], - NativeStyleEditor_styleAndLayout: [StyleAndLayoutPayload], +|}; + +class Bridge< + OutgoingEvents: Object, + IncomingEvents: Object +> extends EventEmitter<{| + ...IncomingEvents, + ...OutgoingEvents, |}> { _isShutdown: boolean = false; _messageQueue: Array = []; @@ -134,7 +151,10 @@ export default class Bridge extends EventEmitter<{| return this._wall; } - send(event: string, payload: any, transferable?: Array) { + send>( + event: EventName, + ...payload: $ElementType + ) { if (this._isShutdown) { console.warn( `Cannot send message "${event}" through a Bridge that has been shutdown.` @@ -150,7 +170,7 @@ export default class Bridge extends EventEmitter<{| // - if there *has* been a message flushed in the last BATCH_DURATION ms // (or we're waiting for our setTimeout-0 to fire), then _timeoutID will // be set, and we'll simply add to the queue and wait for that - this._messageQueue.push(event, payload, transferable); + this._messageQueue.push(event, payload); if (!this._timeoutID) { this._timeoutID = setTimeout(this._flush, 0); } @@ -204,12 +224,8 @@ export default class Bridge extends EventEmitter<{| this._timeoutID = null; if (this._messageQueue.length) { - for (let i = 0; i < this._messageQueue.length; i += 3) { - this._wall.send( - this._messageQueue[i], - this._messageQueue[i + 1], - this._messageQueue[i + 2] - ); + for (let i = 0; i < this._messageQueue.length; i += 2) { + this._wall.send(this._messageQueue[i], ...this._messageQueue[i + 1]); } this._messageQueue.length = 0; @@ -220,3 +236,8 @@ export default class Bridge extends EventEmitter<{| } }; } + +export type BackendBridge = Bridge; +export type FrontendBridge = Bridge; + +export default Bridge; diff --git a/src/devtools/ProfilerStore.js b/src/devtools/ProfilerStore.js index bf2e8544a3f6..eb79c41e65b0 100644 --- a/src/devtools/ProfilerStore.js +++ b/src/devtools/ProfilerStore.js @@ -3,11 +3,11 @@ import EventEmitter from 'events'; import memoize from 'memoize-one'; import throttle from 'lodash.throttle'; -import Bridge from 'src/bridge'; import { prepareProfilingDataFrontendFromBackendAndStore } from './views/Profiler/utils'; import ProfilingCache from './ProfilingCache'; import Store from './store'; +import type { FrontendBridge } from 'src/bridge'; import type { ProfilingDataBackend } from 'src/backend/types'; import type { CommitDataFrontend, @@ -23,7 +23,7 @@ export default class ProfilerStore extends EventEmitter<{| isProfiling: [], profilingData: [], |}> { - _bridge: Bridge; + _bridge: FrontendBridge; // Suspense cache for lazily calculating derived profiling data. _cache: ProfilingCache; @@ -79,7 +79,11 @@ export default class ProfilerStore extends EventEmitter<{| _store: Store; - constructor(bridge: Bridge, store: Store, defaultIsProfiling: boolean) { + constructor( + bridge: FrontendBridge, + store: Store, + defaultIsProfiling: boolean + ) { super(); this._bridge = bridge; diff --git a/src/devtools/index.js b/src/devtools/index.js index b41b51a1102d..33e628e96542 100644 --- a/src/devtools/index.js +++ b/src/devtools/index.js @@ -1,6 +1,6 @@ // @flow -import Bridge from 'src/bridge'; +import type { FrontendBridge } from 'src/bridge'; type Shell = {| connect: (callback: Function) => void, @@ -8,7 +8,7 @@ type Shell = {| |}; export function initDevTools(shell: Shell) { - shell.connect((bridge: Bridge) => { + shell.connect((bridge: FrontendBridge) => { // TODO ... }); } diff --git a/src/devtools/store.js b/src/devtools/store.js index 2c778a0ccd00..5b3e6a34d75f 100644 --- a/src/devtools/store.js +++ b/src/devtools/store.js @@ -2,7 +2,6 @@ import EventEmitter from 'events'; import { inspect } from 'util'; -import Bridge from 'src/bridge'; import { TREE_OPERATION_ADD, TREE_OPERATION_REMOVE, @@ -24,6 +23,7 @@ import ProfilerStore from './ProfilerStore'; import type { Element } from './views/Components/types'; import type { ComponentFilter, ElementType } from '../types'; +import type { FrontendBridge } from 'src/bridge'; const debug = (methodName, ...args) => { if (__DEBUG__) { @@ -71,7 +71,7 @@ export default class Store extends EventEmitter<{| supportsProfiling: [], supportsReloadAndProfile: [], |}> { - _bridge: Bridge; + _bridge: FrontendBridge; _captureScreenshots: boolean = false; @@ -129,7 +129,7 @@ export default class Store extends EventEmitter<{| // Used for windowing purposes. _weightAcrossRoots: number = 0; - constructor(bridge: Bridge, config?: Config) { + constructor(bridge: FrontendBridge, config?: Config) { super(); if (__DEBUG__) { @@ -701,7 +701,7 @@ export default class Store extends EventEmitter<{| validAttributes, }: {| isSupported: boolean, - validAttributes: $ReadOnlyArray, + validAttributes: ?$ReadOnlyArray, |}) => { this._isNativeStyleEditorSupported = isSupported; this._nativeStyleEditorValidAttributes = validAttributes || null; diff --git a/src/devtools/views/Components/HooksTree.js b/src/devtools/views/Components/HooksTree.js index 0c3b3e79b2a5..10df0c470a65 100644 --- a/src/devtools/views/Components/HooksTree.js +++ b/src/devtools/views/Components/HooksTree.js @@ -221,17 +221,19 @@ function HookView({ if (canEditHooks && isStateEditable) { overrideValueFn = (absolutePath: Array, value: any) => { const rendererID = store.getRendererIDForElement(id); - bridge.send('overrideHookState', { - id, - hookID, - // Hooks override function expects a relative path for the specified hook (id), - // starting with its id within the (flat) hooks list structure. - // This relative path does not include the fake tree structure DevTools uses for display, - // so it's important that we remove that part of the path before sending the update. - path: absolutePath.slice(path.length + 1), - rendererID, - value, - }); + if (rendererID !== null) { + bridge.send('overrideHookState', { + id, + hookID, + // Hooks override function expects a relative path for the specified hook (id), + // starting with its id within the (flat) hooks list structure. + // This relative path does not include the fake tree structure DevTools uses for display, + // so it's important that we remove that part of the path before sending the update. + path: absolutePath.slice(path.length + 1), + rendererID, + value, + }); + } }; } diff --git a/src/devtools/views/Components/InspectedElementContext.js b/src/devtools/views/Components/InspectedElementContext.js index 97efcac574ff..31841774eb63 100644 --- a/src/devtools/views/Components/InspectedElementContext.js +++ b/src/devtools/views/Components/InspectedElementContext.js @@ -85,7 +85,9 @@ function InspectedElementContextController({ children }: Props) { const getInspectedElementPath = useCallback( (id: number, path: Array) => { const rendererID = store.getRendererIDForElement(id); - bridge.send('inspectElement', { id, path, rendererID }); + if (rendererID !== null) { + bridge.send('inspectElement', { id, path, rendererID }); + } }, [bridge, store] ); @@ -232,7 +234,9 @@ function InspectedElementContextController({ children }: Props) { const sendRequest = () => { timeoutID = null; - bridge.send('inspectElement', { id: selectedElementID, rendererID }); + if (rendererID !== null) { + bridge.send('inspectElement', { id: selectedElementID, rendererID }); + } }; // Send the initial inspection request. @@ -240,7 +244,9 @@ function InspectedElementContextController({ children }: Props) { sendRequest(); // Update the $r variable. - bridge.send('selectElement', { id: selectedElementID, rendererID }); + if (rendererID !== null) { + bridge.send('selectElement', { id: selectedElementID, rendererID }); + } const onInspectedElement = (data: InspectedElementPayload) => { // If this is the element we requested, wait a little bit and then ask for another update. diff --git a/src/devtools/views/Components/NativeStyleEditor/StyleEditor.js b/src/devtools/views/Components/NativeStyleEditor/StyleEditor.js index 570f473355ee..94d6923c37f1 100644 --- a/src/devtools/views/Components/NativeStyleEditor/StyleEditor.js +++ b/src/devtools/views/Components/NativeStyleEditor/StyleEditor.js @@ -25,22 +25,28 @@ export default function StyleEditor({ id, style }: Props) { const store = useContext(StoreContext); const changeAttribute = (oldName: string, newName: string, value: any) => { - bridge.send('NativeStyleEditor_renameAttribute', { - id, - rendererID: store.getRendererIDForElement(id), - oldName, - newName, - value, - }); + const rendererID = store.getRendererIDForElement(id); + if (rendererID !== null) { + bridge.send('NativeStyleEditor_renameAttribute', { + id, + rendererID, + oldName, + newName, + value, + }); + } }; const changeValue = (name: string, value: any) => { - bridge.send('NativeStyleEditor_setValue', { - id, - rendererID: store.getRendererIDForElement(id), - name, - value, - }); + const rendererID = store.getRendererIDForElement(id); + if (rendererID !== null) { + bridge.send('NativeStyleEditor_setValue', { + id, + rendererID, + name, + value, + }); + } }; const keys = useMemo(() => Array.from(Object.keys(style)), [style]); diff --git a/src/devtools/views/Components/NativeStyleEditor/context.js b/src/devtools/views/Components/NativeStyleEditor/context.js index 927a4d4151d5..f062fef43e57 100644 --- a/src/devtools/views/Components/NativeStyleEditor/context.js +++ b/src/devtools/views/Components/NativeStyleEditor/context.js @@ -136,10 +136,12 @@ function NativeStyleContextController({ children }: Props) { const sendRequest = () => { timeoutID = null; - bridge.send('NativeStyleEditor_measure', { - id: selectedElementID, - rendererID, - }); + if (rendererID !== null) { + bridge.send('NativeStyleEditor_measure', { + id: selectedElementID, + rendererID, + }); + } }; // Send the initial measurement request. diff --git a/src/devtools/views/Components/OwnersListContext.js b/src/devtools/views/Components/OwnersListContext.js index aa7e2dab0503..6da02d3546ed 100644 --- a/src/devtools/views/Components/OwnersListContext.js +++ b/src/devtools/views/Components/OwnersListContext.js @@ -106,8 +106,9 @@ function OwnersListContextController({ children }: Props) { useEffect(() => { if (ownerID !== null) { const rendererID = store.getRendererIDForElement(ownerID); - - bridge.send('getOwnersList', { id: ownerID, rendererID }); + if (rendererID !== null) { + bridge.send('getOwnersList', { id: ownerID, rendererID }); + } } return () => {}; diff --git a/src/devtools/views/Components/SelectedElement.js b/src/devtools/views/Components/SelectedElement.js index 2d3eece42954..ecc12028c94e 100644 --- a/src/devtools/views/Components/SelectedElement.js +++ b/src/devtools/views/Components/SelectedElement.js @@ -140,12 +140,18 @@ export default function SelectedElement(_: Props) { }); } + const rendererID = store.getRendererIDForElement( + nearestSuspenseElementID + ); + // Toggle suspended - bridge.send('overrideSuspense', { - id: nearestSuspenseElementID, - rendererID: store.getRendererIDForElement(nearestSuspenseElementID), - forceFallback: !isSuspended, - }); + if (rendererID !== null) { + bridge.send('overrideSuspense', { + id: nearestSuspenseElementID, + rendererID, + forceFallback: !isSuspended, + }); + } } }, [bridge, dispatch, element, isSuspended, modalDialogDispatch, store]); @@ -280,15 +286,21 @@ function InspectedElementView({ if (type === ElementTypeClass) { overrideContextFn = (path: Array, value: any) => { const rendererID = store.getRendererIDForElement(id); - bridge.send('overrideContext', { id, path, rendererID, value }); + if (rendererID !== null) { + bridge.send('overrideContext', { id, path, rendererID, value }); + } }; overridePropsFn = (path: Array, value: any) => { const rendererID = store.getRendererIDForElement(id); - bridge.send('overrideProps', { id, path, rendererID, value }); + if (rendererID !== null) { + bridge.send('overrideProps', { id, path, rendererID, value }); + } }; overrideStateFn = (path: Array, value: any) => { const rendererID = store.getRendererIDForElement(id); - bridge.send('overrideState', { id, path, rendererID, value }); + if (rendererID !== null) { + bridge.send('overrideState', { id, path, rendererID, value }); + } }; } else if ( (type === ElementTypeFunction || @@ -298,7 +310,9 @@ function InspectedElementView({ ) { overridePropsFn = (path: Array, value: any) => { const rendererID = store.getRendererIDForElement(id); - bridge.send('overrideProps', { id, path, rendererID, value }); + if (rendererID !== null) { + bridge.send('overrideProps', { id, path, rendererID, value }); + } }; } else if (type === ElementTypeSuspense && canToggleSuspense) { overrideSuspenseFn = (path: Array, value: boolean) => { @@ -306,7 +320,13 @@ function InspectedElementView({ throw new Error('Unexpected path.'); } const rendererID = store.getRendererIDForElement(id); - bridge.send('overrideSuspense', { id, rendererID, forceFallback: value }); + if (rendererID !== null) { + bridge.send('overrideSuspense', { + id, + rendererID, + forceFallback: value, + }); + } }; } diff --git a/src/devtools/views/Components/Tree.js b/src/devtools/views/Components/Tree.js index 85d3ef165819..285cb39c4165 100644 --- a/src/devtools/views/Components/Tree.js +++ b/src/devtools/views/Components/Tree.js @@ -192,7 +192,7 @@ export default function Tree(props: Props) { (id: number) => { const element = store.getElementByID(id); const rendererID = store.getRendererIDForElement(id); - if (element !== null) { + if (element !== null && rendererID !== null) { bridge.send('highlightNativeElement', { displayName: element.displayName, hideAfterTimeout: false, diff --git a/src/devtools/views/DevTools.js b/src/devtools/views/DevTools.js index f8d9bcec9186..1aab4afaef5b 100644 --- a/src/devtools/views/DevTools.js +++ b/src/devtools/views/DevTools.js @@ -6,7 +6,6 @@ import '@reach/menu-button/styles.css'; import '@reach/tooltip/styles.css'; import React, { useMemo, useState } from 'react'; -import Bridge from 'src/bridge'; import Store from '../store'; import { BridgeContext, StoreContext } from './context'; import Components from './Components/Components'; @@ -25,6 +24,7 @@ import styles from './DevTools.css'; import './root.css'; import type { InspectedElement } from 'src/devtools/views/Components/types'; +import type { FrontendBridge } from 'src/bridge'; export type BrowserTheme = 'dark' | 'light'; export type TabID = 'components' | 'profiler' | 'settings'; @@ -34,7 +34,7 @@ export type ViewElementSource = ( ) => void; export type Props = {| - bridge: Bridge, + bridge: FrontendBridge, browserTheme?: BrowserTheme, defaultTab?: TabID, showTabBar?: boolean, diff --git a/src/devtools/views/context.js b/src/devtools/views/context.js index e5ede9ef48a0..96c654a94397 100644 --- a/src/devtools/views/context.js +++ b/src/devtools/views/context.js @@ -1,11 +1,13 @@ // @flow import { createContext } from 'react'; -import Bridge from 'src/bridge'; - import Store from '../store'; -export const BridgeContext = createContext(((null: any): Bridge)); +import type { FrontendBridge } from 'src/bridge'; + +export const BridgeContext = createContext( + ((null: any): FrontendBridge) +); BridgeContext.displayName = 'BridgeContext'; export const StoreContext = createContext(((null: any): Store)); diff --git a/vendor/react-window/dist/index-dev.umd.js b/vendor/react-window/dist/index-dev.umd.js new file mode 100644 index 000000000000..96dc2ae17a61 --- /dev/null +++ b/vendor/react-window/dist/index-dev.umd.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t((e=e||self).ReactWindow={},e.React)}(this,function(e,t){"use strict";function r(){return(r=Object.assign||function(e){for(var t=1;t=t?e.call(null):n.id=requestAnimationFrame(o)})};return n}var u=-1;var d=null;function f(e){if(void 0===e&&(e=!1),null===d||e){var t=document.createElement("div"),r=t.style;r.width="50px",r.height="50px",r.overflow="scroll",r.direction="rtl";var n=document.createElement("div"),o=n.style;return o.width="100px",o.height="100px",t.appendChild(n),document.body.appendChild(t),t.scrollLeft>0?d="positive-descending":(t.scrollLeft=1,d=0===t.scrollLeft?"negative":"positive-ascending"),document.body.removeChild(t),d}return d}var h=150,p=function(e){var t=e.columnIndex;e.data;return e.rowIndex+":"+t},m=null,v=null,g=null;function w(e){var i,l,d=e.getColumnOffset,m=e.getColumnStartIndexForOffset,v=e.getColumnStopIndexForStartIndex,g=e.getColumnWidth,w=e.getEstimatedTotalHeight,I=e.getEstimatedTotalWidth,M=e.getOffsetForColumnAndAlignment,y=e.getOffsetForRowAndAlignment,x=e.getRowHeight,C=e.getRowOffset,_=e.getRowStartIndexForOffset,b=e.getRowStopIndexForStartIndex,R=e.initInstanceProps,T=e.shouldResetStyleCacheOnItemSizeChange,z=e.validateProps;return l=i=function(e){function i(t){var r;return(r=e.call(this,t)||this)._instanceProps=R(r.props,o(o(r))),r._resetIsScrollingTimeoutId=null,r._outerRef=void 0,r.state={instance:o(o(r)),isScrolling:!1,horizontalScrollDirection:"forward",scrollLeft:"number"==typeof r.props.initialScrollLeft?r.props.initialScrollLeft:0,scrollTop:"number"==typeof r.props.initialScrollTop?r.props.initialScrollTop:0,scrollUpdateWasRequested:!1,verticalScrollDirection:"forward"},r._callOnItemsRendered=void 0,r._callOnItemsRendered=a(function(e,t,n,o,i,a,l,s){return r.props.onItemsRendered({overscanColumnStartIndex:e,overscanColumnStopIndex:t,overscanRowStartIndex:n,overscanRowStopIndex:o,visibleColumnStartIndex:i,visibleColumnStopIndex:a,visibleRowStartIndex:l,visibleRowStopIndex:s})}),r._callOnScroll=void 0,r._callOnScroll=a(function(e,t,n,o,i){return r.props.onScroll({horizontalScrollDirection:n,scrollLeft:e,scrollTop:t,verticalScrollDirection:o,scrollUpdateWasRequested:i})}),r._getItemStyle=void 0,r._getItemStyle=function(e,t){var n,o,i=r.props,a=i.columnWidth,l=i.direction,s=i.rowHeight,c=r._getItemStyleCache(T&&a,T&&l,T&&s),u=e+":"+t;c.hasOwnProperty(u)?n=c[u]:c[u]=((o={position:"absolute"})["rtl"===l?"right":"left"]=d(r.props,t,r._instanceProps),o.top=C(r.props,e,r._instanceProps),o.height=x(r.props,e,r._instanceProps),o.width=g(r.props,t,r._instanceProps),n=o);return n},r._getItemStyleCache=void 0,r._getItemStyleCache=a(function(e,t,r){return{}}),r._onScroll=function(e){var t=e.currentTarget,n=t.clientHeight,o=t.clientWidth,i=t.scrollLeft,a=t.scrollTop,l=t.scrollHeight,s=t.scrollWidth;r.setState(function(e){if(e.scrollLeft===i&&e.scrollTop===a)return null;var t=r.props.direction,c=i;if("rtl"===t)switch(f()){case"negative":c=-i;break;case"positive-descending":c=s-o-i}c=Math.max(0,Math.min(c,s-o));var u=Math.max(0,Math.min(a,l-n));return{isScrolling:!0,horizontalScrollDirection:e.scrollLeftc?p:0,g=m>l?p:0;this.scrollTo({scrollLeft:void 0!==n?M(this.props,n,r,f,this._instanceProps,g):f,scrollTop:void 0!==o?y(this.props,o,r,h,this._instanceProps,v):h})},l.componentDidMount=function(){var e=this.props,t=e.initialScrollLeft,r=e.initialScrollTop;if(null!=this._outerRef){var n=this._outerRef;"number"==typeof t&&(n.scrollLeft=t),"number"==typeof r&&(n.scrollTop=r)}this._callPropsCallbacks()},l.componentDidUpdate=function(){var e=this.props.direction,t=this.state,r=t.scrollLeft,n=t.scrollTop;if(t.scrollUpdateWasRequested&&null!=this._outerRef){var o=this._outerRef;if("rtl"===e)switch(f()){case"negative":o.scrollLeft=-r;break;case"positive-ascending":o.scrollLeft=r;break;default:var i=o.clientWidth,a=o.scrollWidth;o.scrollLeft=a-i-r}else o.scrollLeft=Math.max(0,r);o.scrollTop=Math.max(0,n)}this._callPropsCallbacks()},l.componentWillUnmount=function(){null!==this._resetIsScrollingTimeoutId&&s(this._resetIsScrollingTimeoutId)},l.render=function(){var e=this.props,n=e.children,o=e.className,i=e.columnCount,a=e.direction,l=e.height,s=e.innerRef,c=e.innerElementType,u=e.innerTagName,d=e.itemData,f=e.itemKey,h=void 0===f?p:f,m=e.outerElementType,v=e.outerTagName,g=e.rowCount,S=e.style,M=e.useIsScrolling,y=e.width,x=this.state.isScrolling,C=this._getHorizontalRangeToRender(),_=C[0],b=C[1],R=this._getVerticalRangeToRender(),T=R[0],z=R[1],O=[];if(i>0&&g)for(var P=T;P<=z;P++)for(var W=_;W<=b;W++)O.push(t.createElement(n,{columnIndex:W,data:d,isScrolling:M?x:void 0,key:h({columnIndex:W,data:d,rowIndex:P}),rowIndex:P,style:this._getItemStyle(P,W)}));var E=w(this.props,this._instanceProps),A=I(this.props,this._instanceProps);return t.createElement(m||v||"div",{className:o,onScroll:this._onScroll,ref:this._outerRefSetter,style:r({position:"relative",height:l,width:y,overflow:"auto",WebkitOverflowScrolling:"touch",willChange:"transform",direction:a},S)},t.createElement(c||u||"div",{children:O,ref:s,style:{height:E,pointerEvents:x?"none":void 0,width:A}}))},l._callPropsCallbacks=function(){var e=this.props,t=e.columnCount,r=e.onItemsRendered,n=e.onScroll,o=e.rowCount;if("function"==typeof r&&t>0&&o>0){var i=this._getHorizontalRangeToRender(),a=i[0],l=i[1],s=i[2],c=i[3],u=this._getVerticalRangeToRender(),d=u[0],f=u[1],h=u[2],p=u[3];this._callOnItemsRendered(a,l,d,f,s,c,h,p)}if("function"==typeof n){var m=this.state,v=m.horizontalScrollDirection,g=m.scrollLeft,w=m.scrollTop,S=m.scrollUpdateWasRequested,I=m.verticalScrollDirection;this._callOnScroll(g,w,v,I,S)}},l._getHorizontalRangeToRender=function(){var e=this.props,t=e.columnCount,r=e.overscanColumnCount,n=e.overscanColumnsCount,o=e.overscanCount,i=e.rowCount,a=this.state,l=a.horizontalScrollDirection,s=a.isScrolling,c=a.scrollLeft,u=r||n||o||1;if(0===t||0===i)return[0,0,0,0];var d=m(this.props,c,this._instanceProps),f=v(this.props,d,c,this._instanceProps),h=s&&"backward"!==l?1:Math.max(1,u),p=s&&"forward"!==l?1:Math.max(1,u);return[Math.max(0,d-h),Math.max(0,Math.min(t-1,f+p)),d,f]},l._getVerticalRangeToRender=function(){var e=this.props,t=e.columnCount,r=e.overscanCount,n=e.overscanRowCount,o=e.overscanRowsCount,i=e.rowCount,a=this.state,l=a.isScrolling,s=a.verticalScrollDirection,c=a.scrollTop,u=n||o||r||1;if(0===t||0===i)return[0,0,0,0];var d=_(this.props,c,this._instanceProps),f=b(this.props,d,c,this._instanceProps),h=l&&"backward"!==s?1:Math.max(1,u),p=l&&"forward"!==s?1:Math.max(1,u);return[Math.max(0,d-h),Math.max(0,Math.min(i-1,f+p)),d,f]},i}(t.PureComponent),i.defaultProps={direction:"ltr",itemData:void 0,useIsScrolling:!1},l}"undefined"!=typeof window&&void 0!==window.WeakSet&&(m=new WeakSet,v=new WeakSet,g=new WeakSet);var S=function(e,t){var r=e.children,n=e.direction,o=e.height,i=e.innerTagName,a=e.outerTagName,l=e.overscanColumnsCount,s=e.overscanCount,c=e.overscanRowsCount,u=e.width,d=t.instance;if("number"==typeof s&&m&&!m.has(d)&&(m.add(d),console.warn("The overscanCount prop has been deprecated. Please use the overscanColumnCount and overscanRowCount props instead.")),"number"!=typeof l&&"number"!=typeof c||v&&!v.has(d)&&(v.add(d),console.warn("The overscanColumnsCount and overscanRowsCount props have been deprecated. Please use the overscanColumnCount and overscanRowCount props instead.")),null==i&&null==a||g&&!g.has(d)&&(g.add(d),console.warn("The innerTagName and outerTagName props have been deprecated. Please use the innerElementType and outerElementType props instead.")),null==r)throw Error('An invalid "children" prop has been specified. Value should be a React component. "'+(null===r?"null":typeof r)+'" was specified.');switch(n){case"ltr":case"rtl":break;default:throw Error('An invalid "direction" prop has been specified. Value should be either "ltr" or "rtl". "'+n+'" was specified.')}if("number"!=typeof u)throw Error('An invalid "width" prop has been specified. Grids must specify a number for width. "'+(null===u?"null":typeof u)+'" was specified.');if("number"!=typeof o)throw Error('An invalid "height" prop has been specified. Grids must specify a number for height. "'+(null===o?"null":typeof o)+'" was specified.')},I=function(e,t){var r=e.rowCount,n=t.rowMetadataMap,o=t.estimatedRowHeight,i=t.lastMeasuredRowIndex,a=0;if(i>=r&&(i=r-1),i>=0){var l=n[i];a=l.offset+l.size}return a+(r-i-1)*o},M=function(e,t){var r=e.columnCount,n=t.columnMetadataMap,o=t.estimatedColumnWidth,i=t.lastMeasuredColumnIndex,a=0;if(i>=r&&(i=r-1),i>=0){var l=n[i];a=l.offset+l.size}return a+(r-i-1)*o},y=function(e,t,r,n){var o,i,a;if("column"===e?(o=n.columnMetadataMap,i=t.columnWidth,a=n.lastMeasuredColumnIndex):(o=n.rowMetadataMap,i=t.rowHeight,a=n.lastMeasuredRowIndex),r>a){var l=0;if(a>=0){var s=o[a];l=s.offset+s.size}for(var c=a+1;c<=r;c++){var u=i(c);o[c]={offset:l,size:u},l+=u}"column"===e?n.lastMeasuredColumnIndex=r:n.lastMeasuredRowIndex=r}return o[r]},x=function(e,t,r,n){var o,i;return"column"===e?(o=r.columnMetadataMap,i=r.lastMeasuredColumnIndex):(o=r.rowMetadataMap,i=r.lastMeasuredRowIndex),(i>0?o[i].offset:0)>=n?C(e,t,r,i,0,n):_(e,t,r,Math.max(0,i),n)},C=function(e,t,r,n,o,i){for(;o<=n;){var a=o+Math.floor((n-o)/2),l=y(e,t,a,r).offset;if(l===i)return a;li&&(n=a-1)}return o>0?o-1:0},_=function(e,t,r,n,o){for(var i="column"===e?t.columnCount:t.rowCount,a=1;n=d-l&&o<=u+l?"auto":"center"),n){case"start":return u;case"end":return d;case"center":return Math.round(d+(u-d)/2);case"auto":default:return o>=d&&o<=u?o:d>u?d:o0)for(var T=_;T<=b;T++)R.push(t.createElement(n,{data:f,key:p(T,f),index:T,isScrolling:S?M:void 0,style:this._getItemStyle(T)}));var O=d(this.props,this._instanceProps);return t.createElement(v||g||"div",{className:o,onScroll:x,ref:this._outerRefSetter,style:r({position:"relative",height:a,width:I,overflow:"auto",WebkitOverflowScrolling:"touch",willChange:"transform",direction:i},w)},t.createElement(s||c||"div",{children:R,ref:l,style:{height:y?"100%":O,pointerEvents:M?"none":void 0,width:y?O:"100%"}}))},l._callPropsCallbacks=function(){if("function"==typeof this.props.onItemsRendered&&this.props.itemCount>0){var e=this._getRangeToRender(),t=e[0],r=e[1],n=e[2],o=e[3];this._callOnItemsRendered(t,r,n,o)}if("function"==typeof this.props.onScroll){var i=this.state,a=i.scrollDirection,l=i.scrollOffset,s=i.scrollUpdateWasRequested;this._callOnScroll(a,l,s)}},l._getRangeToRender=function(){var e=this.props,t=e.itemCount,r=e.overscanCount,n=this.state,o=n.isScrolling,i=n.scrollDirection,a=n.scrollOffset;if(0===t)return[0,0,0,0];var l=m(this.props,a,this._instanceProps),s=v(this.props,l,a,this._instanceProps),c=o&&"backward"!==i?1:Math.max(1,r),u=o&&"forward"!==i?1:Math.max(1,r);return[Math.max(0,l-c),Math.max(0,Math.min(t-1,s+u)),l,s]},i}(t.PureComponent),i.defaultProps={direction:"ltr",itemData:void 0,layout:"vertical",overscanCount:2,useIsScrolling:!1},l}"undefined"!=typeof window&&void 0!==window.WeakSet&&(O=new WeakSet,P=new WeakSet);var E=function(e,t){var r=e.children,n=e.direction,o=e.height,i=e.layout,a=e.innerTagName,l=e.outerTagName,s=e.width,c=t.instance;null==a&&null==l||P&&!P.has(c)&&(P.add(c),console.warn("The innerTagName and outerTagName props have been deprecated. Please use the innerElementType and outerElementType props instead."));var u="horizontal"===n||"horizontal"===i;switch(n){case"horizontal":case"vertical":O&&!O.has(c)&&(O.add(c),console.warn('The direction prop should be either "ltr" (default) or "rtl". Please use the layout prop to specify "vertical" (default) or "horizontal" orientation.'));break;case"ltr":case"rtl":break;default:throw Error('An invalid "direction" prop has been specified. Value should be either "ltr" or "rtl". "'+n+'" was specified.')}switch(i){case"horizontal":case"vertical":break;default:throw Error('An invalid "layout" prop has been specified. Value should be either "horizontal" or "vertical". "'+i+'" was specified.')}if(null==r)throw Error('An invalid "children" prop has been specified. Value should be a React component. "'+(null===r?"null":typeof r)+'" was specified.');if(u&&"number"!=typeof s)throw Error('An invalid "width" prop has been specified. Horizontal lists must specify a number for width. "'+(null===s?"null":typeof s)+'" was specified.');if(!u&&"number"!=typeof o)throw Error('An invalid "height" prop has been specified. Vertical lists must specify a number for height. "'+(null===o?"null":typeof o)+'" was specified.')},A=function(e,t,r){var n=e.itemSize,o=r.itemMetadataMap,i=r.lastMeasuredIndex;if(t>i){var a=0;if(i>=0){var l=o[i];a=l.offset+l.size}for(var s=i+1;s<=t;s++){var c=n(s);o[s]={offset:a,size:c},a+=c}r.lastMeasuredIndex=t}return o[t]},k=function(e,t,r,n,o){for(;n<=r;){var i=n+Math.floor((r-n)/2),a=A(e,i,t).offset;if(a===o)return i;ao&&(r=i-1)}return n>0?n-1:0},D=function(e,t,r,n){for(var o=e.itemCount,i=1;r=r&&(i=r-1),i>=0){var l=n[i];a=l.offset+l.size}return a+(r-i-1)*o},L=W({getItemOffset:function(e,t,r){return A(e,t,r).offset},getItemSize:function(e,t,r){return r.itemMetadataMap[t].size},getEstimatedTotalSize:F,getOffsetForIndexAndAlignment:function(e,t,r,n,o){var i=e.direction,a=e.height,l=e.layout,s=e.width,c="horizontal"===i||"horizontal"===l?s:a,u=A(e,t,o),d=F(e,o),f=Math.max(0,Math.min(d-c,u.offset)),h=Math.max(0,u.offset-c+u.size);switch("smart"===r&&(r=n>=h-c&&n<=f+c?"auto":"center"),r){case"start":return f;case"end":return h;case"center":return Math.round(h+(f-h)/2);case"auto":default:return n>=h&&n<=f?n:n0?n[o].offset:0)>=r?k(e,t,o,0,r):D(e,t,Math.max(0,o),r)}(e,r,t)},getStopIndexForStartIndex:function(e,t,r,n){for(var o=e.direction,i=e.height,a=e.itemCount,l=e.layout,s=e.width,c="horizontal"===o||"horizontal"===l?s:i,u=A(e,t,n),d=r+c,f=u.offset+u.size,h=t;h=d-s&&n<=u+s?"auto":"center"),r){case"start":return u;case"end":return d;case"center":var f=Math.round(d+(u-d)/2);return fc+Math.floor(s/2)?c:f;case"auto":default:return n>=d&&n<=u?n:d>u?d:n=d-l&&n<=u+l?"auto":"center"),r){case"start":return u;case"end":return d;case"center":var f=Math.round(d+(u-d)/2);return fc+Math.floor(l/2)?c:f;case"auto":default:return n>=d&&n<=u?n:d>u?d:n=h-u&&n<=f+u?"auto":"center"),r){case"start":return f;case"end":return h;case"center":var p=Math.round(h+(f-h)/2);return pd+Math.floor(u/2)?d:p;case"auto":default:return n>=h&&n<=f?n:n=0||(o[r]=e[r]);return o}function q(e,t){for(var r in e)if(!(r in t))return!0;for(var n in t)if(e[n]!==t[n])return!0;return!1}function N(e,t){var r=e.style,n=V(e,["style"]),o=t.style,i=V(t,["style"]);return!q(r,o)&&!q(n,i)}e.VariableSizeGrid=R,e.VariableSizeList=L,e.FixedSizeGrid=H,e.FixedSizeList=U,e.areEqual=N,e.shouldComponentUpdate=function(e,t){return!N(this.props,e)||q(this.state,t)},Object.defineProperty(e,"__esModule",{value:!0})}); +//# sourceMappingURL=index-dev.umd.js.map diff --git a/vendor/react-window/dist/index-prod.umd.js b/vendor/react-window/dist/index-prod.umd.js new file mode 100644 index 000000000000..ca33541ed54f --- /dev/null +++ b/vendor/react-window/dist/index-prod.umd.js @@ -0,0 +1,2 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],e):e((t=t||self).ReactWindow={},t.React)}(this,function(t,e){"use strict";function r(){return(r=Object.assign||function(t){for(var e=1;e=e?t.call(null):o.id=requestAnimationFrame(n)})};return o}var u=-1;var f=null;function d(t){if(void 0===t&&(t=!1),null===f||t){var e=document.createElement("div"),r=e.style;r.width="50px",r.height="50px",r.overflow="scroll",r.direction="rtl";var o=document.createElement("div"),n=o.style;return n.width="100px",n.height="100px",e.appendChild(o),document.body.appendChild(e),e.scrollLeft>0?f="positive-descending":(e.scrollLeft=1,f=0===e.scrollLeft?"negative":"positive-ascending"),document.body.removeChild(e),f}return f}var h=150,m=function(t){var e=t.columnIndex;t.data;return t.rowIndex+":"+e};function p(t){var i,l,f=t.getColumnOffset,p=t.getColumnStartIndexForOffset,v=t.getColumnStopIndexForStartIndex,S=t.getColumnWidth,I=t.getEstimatedTotalHeight,w=t.getEstimatedTotalWidth,M=t.getOffsetForColumnAndAlignment,x=t.getOffsetForRowAndAlignment,_=t.getRowHeight,C=t.getRowOffset,R=t.getRowStartIndexForOffset,y=t.getRowStopIndexForStartIndex,T=t.initInstanceProps,O=t.shouldResetStyleCacheOnItemSizeChange,z=t.validateProps;return l=i=function(t){function i(e){var r;return(r=t.call(this,e)||this)._instanceProps=T(r.props,n(n(r))),r._resetIsScrollingTimeoutId=null,r._outerRef=void 0,r.state={instance:n(n(r)),isScrolling:!1,horizontalScrollDirection:"forward",scrollLeft:"number"==typeof r.props.initialScrollLeft?r.props.initialScrollLeft:0,scrollTop:"number"==typeof r.props.initialScrollTop?r.props.initialScrollTop:0,scrollUpdateWasRequested:!1,verticalScrollDirection:"forward"},r._callOnItemsRendered=void 0,r._callOnItemsRendered=a(function(t,e,o,n,i,a,l,s){return r.props.onItemsRendered({overscanColumnStartIndex:t,overscanColumnStopIndex:e,overscanRowStartIndex:o,overscanRowStopIndex:n,visibleColumnStartIndex:i,visibleColumnStopIndex:a,visibleRowStartIndex:l,visibleRowStopIndex:s})}),r._callOnScroll=void 0,r._callOnScroll=a(function(t,e,o,n,i){return r.props.onScroll({horizontalScrollDirection:o,scrollLeft:t,scrollTop:e,verticalScrollDirection:n,scrollUpdateWasRequested:i})}),r._getItemStyle=void 0,r._getItemStyle=function(t,e){var o,n,i=r.props,a=i.columnWidth,l=i.direction,s=i.rowHeight,c=r._getItemStyleCache(O&&a,O&&l,O&&s),u=t+":"+e;c.hasOwnProperty(u)?o=c[u]:c[u]=((n={position:"absolute"})["rtl"===l?"right":"left"]=f(r.props,e,r._instanceProps),n.top=C(r.props,t,r._instanceProps),n.height=_(r.props,t,r._instanceProps),n.width=S(r.props,e,r._instanceProps),o=n);return o},r._getItemStyleCache=void 0,r._getItemStyleCache=a(function(t,e,r){return{}}),r._onScroll=function(t){var e=t.currentTarget,o=e.clientHeight,n=e.clientWidth,i=e.scrollLeft,a=e.scrollTop,l=e.scrollHeight,s=e.scrollWidth;r.setState(function(t){if(t.scrollLeft===i&&t.scrollTop===a)return null;var e=r.props.direction,c=i;if("rtl"===e)switch(d()){case"negative":c=-i;break;case"positive-descending":c=s-n-i}c=Math.max(0,Math.min(c,s-n));var u=Math.max(0,Math.min(a,l-o));return{isScrolling:!0,horizontalScrollDirection:t.scrollLeftc?m:0,v=p>l?m:0;this.scrollTo({scrollLeft:void 0!==o?M(this.props,o,r,d,this._instanceProps,v):d,scrollTop:void 0!==n?x(this.props,n,r,h,this._instanceProps,g):h})},l.componentDidMount=function(){var t=this.props,e=t.initialScrollLeft,r=t.initialScrollTop;if(null!=this._outerRef){var o=this._outerRef;"number"==typeof e&&(o.scrollLeft=e),"number"==typeof r&&(o.scrollTop=r)}this._callPropsCallbacks()},l.componentDidUpdate=function(){var t=this.props.direction,e=this.state,r=e.scrollLeft,o=e.scrollTop;if(e.scrollUpdateWasRequested&&null!=this._outerRef){var n=this._outerRef;if("rtl"===t)switch(d()){case"negative":n.scrollLeft=-r;break;case"positive-ascending":n.scrollLeft=r;break;default:var i=n.clientWidth,a=n.scrollWidth;n.scrollLeft=a-i-r}else n.scrollLeft=Math.max(0,r);n.scrollTop=Math.max(0,o)}this._callPropsCallbacks()},l.componentWillUnmount=function(){null!==this._resetIsScrollingTimeoutId&&s(this._resetIsScrollingTimeoutId)},l.render=function(){var t=this.props,o=t.children,n=t.className,i=t.columnCount,a=t.direction,l=t.height,s=t.innerRef,c=t.innerElementType,u=t.innerTagName,f=t.itemData,d=t.itemKey,h=void 0===d?m:d,p=t.outerElementType,g=t.outerTagName,v=t.rowCount,S=t.style,M=t.useIsScrolling,x=t.width,_=this.state.isScrolling,C=this._getHorizontalRangeToRender(),R=C[0],y=C[1],T=this._getVerticalRangeToRender(),O=T[0],z=T[1],b=[];if(i>0&&v)for(var P=O;P<=z;P++)for(var W=R;W<=y;W++)b.push(e.createElement(o,{columnIndex:W,data:f,isScrolling:M?_:void 0,key:h({columnIndex:W,data:f,rowIndex:P}),rowIndex:P,style:this._getItemStyle(P,W)}));var D=I(this.props,this._instanceProps),F=w(this.props,this._instanceProps);return e.createElement(p||g||"div",{className:n,onScroll:this._onScroll,ref:this._outerRefSetter,style:r({position:"relative",height:l,width:x,overflow:"auto",WebkitOverflowScrolling:"touch",willChange:"transform",direction:a},S)},e.createElement(c||u||"div",{children:b,ref:s,style:{height:D,pointerEvents:_?"none":void 0,width:F}}))},l._callPropsCallbacks=function(){var t=this.props,e=t.columnCount,r=t.onItemsRendered,o=t.onScroll,n=t.rowCount;if("function"==typeof r&&e>0&&n>0){var i=this._getHorizontalRangeToRender(),a=i[0],l=i[1],s=i[2],c=i[3],u=this._getVerticalRangeToRender(),f=u[0],d=u[1],h=u[2],m=u[3];this._callOnItemsRendered(a,l,f,d,s,c,h,m)}if("function"==typeof o){var p=this.state,g=p.horizontalScrollDirection,v=p.scrollLeft,S=p.scrollTop,I=p.scrollUpdateWasRequested,w=p.verticalScrollDirection;this._callOnScroll(v,S,g,w,I)}},l._getHorizontalRangeToRender=function(){var t=this.props,e=t.columnCount,r=t.overscanColumnCount,o=t.overscanColumnsCount,n=t.overscanCount,i=t.rowCount,a=this.state,l=a.horizontalScrollDirection,s=a.isScrolling,c=a.scrollLeft,u=r||o||n||1;if(0===e||0===i)return[0,0,0,0];var f=p(this.props,c,this._instanceProps),d=v(this.props,f,c,this._instanceProps),h=s&&"backward"!==l?1:Math.max(1,u),m=s&&"forward"!==l?1:Math.max(1,u);return[Math.max(0,f-h),Math.max(0,Math.min(e-1,d+m)),f,d]},l._getVerticalRangeToRender=function(){var t=this.props,e=t.columnCount,r=t.overscanCount,o=t.overscanRowCount,n=t.overscanRowsCount,i=t.rowCount,a=this.state,l=a.isScrolling,s=a.verticalScrollDirection,c=a.scrollTop,u=o||n||r||1;if(0===e||0===i)return[0,0,0,0];var f=R(this.props,c,this._instanceProps),d=y(this.props,f,c,this._instanceProps),h=l&&"backward"!==s?1:Math.max(1,u),m=l&&"forward"!==s?1:Math.max(1,u);return[Math.max(0,f-h),Math.max(0,Math.min(i-1,d+m)),f,d]},i}(e.PureComponent),i.defaultProps={direction:"ltr",itemData:void 0,useIsScrolling:!1},l}var g=function(t,e){t.children,t.direction,t.height,t.innerTagName,t.outerTagName,t.overscanColumnsCount,t.overscanCount,t.overscanRowsCount,t.width,e.instance},v=function(t,e){var r=t.rowCount,o=e.rowMetadataMap,n=e.estimatedRowHeight,i=e.lastMeasuredRowIndex,a=0;if(i>=r&&(i=r-1),i>=0){var l=o[i];a=l.offset+l.size}return a+(r-i-1)*n},S=function(t,e){var r=t.columnCount,o=e.columnMetadataMap,n=e.estimatedColumnWidth,i=e.lastMeasuredColumnIndex,a=0;if(i>=r&&(i=r-1),i>=0){var l=o[i];a=l.offset+l.size}return a+(r-i-1)*n},I=function(t,e,r,o){var n,i,a;if("column"===t?(n=o.columnMetadataMap,i=e.columnWidth,a=o.lastMeasuredColumnIndex):(n=o.rowMetadataMap,i=e.rowHeight,a=o.lastMeasuredRowIndex),r>a){var l=0;if(a>=0){var s=n[a];l=s.offset+s.size}for(var c=a+1;c<=r;c++){var u=i(c);n[c]={offset:l,size:u},l+=u}"column"===t?o.lastMeasuredColumnIndex=r:o.lastMeasuredRowIndex=r}return n[r]},w=function(t,e,r,o){var n,i;return"column"===t?(n=r.columnMetadataMap,i=r.lastMeasuredColumnIndex):(n=r.rowMetadataMap,i=r.lastMeasuredRowIndex),(i>0?n[i].offset:0)>=o?M(t,e,r,i,0,o):x(t,e,r,Math.max(0,i),o)},M=function(t,e,r,o,n,i){for(;n<=o;){var a=n+Math.floor((o-n)/2),l=I(t,e,a,r).offset;if(l===i)return a;li&&(o=a-1)}return n>0?n-1:0},x=function(t,e,r,o,n){for(var i="column"===t?e.columnCount:e.rowCount,a=1;o=f-l&&n<=u+l?"auto":"center"),o){case"start":return u;case"end":return f;case"center":return Math.round(f+(u-f)/2);case"auto":default:return n>=f&&n<=u?n:f>u?f:n0)for(var z=R;z<=T;z++)O.push(e.createElement(o,{data:d,key:m(z,d),index:z,isScrolling:I?M:void 0,style:this._getItemStyle(z)}));var b=f(this.props,this._instanceProps);return e.createElement(g||v||"div",{className:n,onScroll:_,ref:this._outerRefSetter,style:r({position:"relative",height:a,width:w,overflow:"auto",WebkitOverflowScrolling:"touch",willChange:"transform",direction:i},S)},e.createElement(s||c||"div",{children:O,ref:l,style:{height:x?"100%":b,pointerEvents:M?"none":void 0,width:x?b:"100%"}}))},l._callPropsCallbacks=function(){if("function"==typeof this.props.onItemsRendered&&this.props.itemCount>0){var t=this._getRangeToRender(),e=t[0],r=t[1],o=t[2],n=t[3];this._callOnItemsRendered(e,r,o,n)}if("function"==typeof this.props.onScroll){var i=this.state,a=i.scrollDirection,l=i.scrollOffset,s=i.scrollUpdateWasRequested;this._callOnScroll(a,l,s)}},l._getRangeToRender=function(){var t=this.props,e=t.itemCount,r=t.overscanCount,o=this.state,n=o.isScrolling,i=o.scrollDirection,a=o.scrollOffset;if(0===e)return[0,0,0,0];var l=p(this.props,a,this._instanceProps),s=g(this.props,l,a,this._instanceProps),c=n&&"backward"!==i?1:Math.max(1,r),u=n&&"forward"!==i?1:Math.max(1,r);return[Math.max(0,l-c),Math.max(0,Math.min(e-1,s+u)),l,s]},i}(e.PureComponent),i.defaultProps={direction:"ltr",itemData:void 0,layout:"vertical",overscanCount:2,useIsScrolling:!1},l}var O=function(t,e){t.children,t.direction,t.height,t.layout,t.innerTagName,t.outerTagName,t.width,e.instance},z=function(t,e,r){var o=t.itemSize,n=r.itemMetadataMap,i=r.lastMeasuredIndex;if(e>i){var a=0;if(i>=0){var l=n[i];a=l.offset+l.size}for(var s=i+1;s<=e;s++){var c=o(s);n[s]={offset:a,size:c},a+=c}r.lastMeasuredIndex=e}return n[e]},b=function(t,e,r,o,n){for(;o<=r;){var i=o+Math.floor((r-o)/2),a=z(t,i,e).offset;if(a===n)return i;an&&(r=i-1)}return o>0?o-1:0},P=function(t,e,r,o){for(var n=t.itemCount,i=1;r=r&&(i=r-1),i>=0){var l=o[i];a=l.offset+l.size}return a+(r-i-1)*n},D=T({getItemOffset:function(t,e,r){return z(t,e,r).offset},getItemSize:function(t,e,r){return r.itemMetadataMap[e].size},getEstimatedTotalSize:W,getOffsetForIndexAndAlignment:function(t,e,r,o,n){var i=t.direction,a=t.height,l=t.layout,s=t.width,c="horizontal"===i||"horizontal"===l?s:a,u=z(t,e,n),f=W(t,n),d=Math.max(0,Math.min(f-c,u.offset)),h=Math.max(0,u.offset-c+u.size);switch("smart"===r&&(r=o>=h-c&&o<=d+c?"auto":"center"),r){case"start":return d;case"end":return h;case"center":return Math.round(h+(d-h)/2);case"auto":default:return o>=h&&o<=d?o:o0?o[n].offset:0)>=r?b(t,e,n,0,r):P(t,e,Math.max(0,n),r)}(t,r,e)},getStopIndexForStartIndex:function(t,e,r,o){for(var n=t.direction,i=t.height,a=t.itemCount,l=t.layout,s=t.width,c="horizontal"===n||"horizontal"===l?s:i,u=z(t,e,o),f=r+c,d=u.offset+u.size,h=e;h=f-s&&o<=u+s?"auto":"center"),r){case"start":return u;case"end":return f;case"center":var d=Math.round(f+(u-f)/2);return dc+Math.floor(s/2)?c:d;case"auto":default:return o>=f&&o<=u?o:f>u?f:o=f-l&&o<=u+l?"auto":"center"),r){case"start":return u;case"end":return f;case"center":var d=Math.round(f+(u-f)/2);return dc+Math.floor(l/2)?c:d;case"auto":default:return o>=f&&o<=u?o:f>u?f:o=h-u&&o<=d+u?"auto":"center"),r){case"start":return d;case"end":return h;case"center":var m=Math.round(h+(d-h)/2);return mf+Math.floor(u/2)?f:m;case"auto":default:return o>=h&&o<=d?o:o=0||(n[r]=t[r]);return n}function H(t,e){for(var r in t)if(!(r in e))return!0;for(var o in e)if(t[o]!==e[o])return!0;return!1}function k(t,e){var r=t.style,o=A(t,["style"]),n=e.style,i=A(e,["style"]);return!H(r,n)&&!H(o,i)}t.VariableSizeGrid=C,t.VariableSizeList=D,t.FixedSizeGrid=F,t.FixedSizeList=L,t.areEqual=k,t.shouldComponentUpdate=function(t,e){return!k(this.props,t)||H(this.state,e)},Object.defineProperty(t,"__esModule",{value:!0})}); +//# sourceMappingURL=index-prod.umd.js.map diff --git a/vendor/react-window/dist/index.cjs.js b/vendor/react-window/dist/index.cjs.js index 08582cdb6fc5..83875b5ee019 100644 --- a/vendor/react-window/dist/index.cjs.js +++ b/vendor/react-window/dist/index.cjs.js @@ -59,6 +59,50 @@ function getScrollbarSize(recalculate) { return size; } +var cachedRTLResult = null; // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. +// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). +// Safari's elastic bounce makes detecting this even more complicated wrt potential false positives. +// The safest way to check this is to intentionally set a negative offset, +// and then verify that the subsequent "scroll" event matches the negative offset. +// If it does not match, then we can assume a non-standard RTL scroll implementation. + +function getRTLOffsetType(recalculate) { + if (recalculate === void 0) { + recalculate = false; + } + + if (cachedRTLResult === null || recalculate) { + var outerDiv = document.createElement('div'); + var outerStyle = outerDiv.style; + outerStyle.width = '50px'; + outerStyle.height = '50px'; + outerStyle.overflow = 'scroll'; + outerStyle.direction = 'rtl'; + var innerDiv = document.createElement('div'); + var innerStyle = innerDiv.style; + innerStyle.width = '100px'; + innerStyle.height = '100px'; + outerDiv.appendChild(innerDiv); + document.body.appendChild(outerDiv); + + if (outerDiv.scrollLeft > 0) { + cachedRTLResult = 'positive-descending'; + } else { + outerDiv.scrollLeft = 1; + + if (outerDiv.scrollLeft === 0) { + cachedRTLResult = 'negative'; + } else { + cachedRTLResult = 'positive-ascending'; + } + } + + document.body.removeChild(outerDiv); + return cachedRTLResult; + } + + return cachedRTLResult; +} var IS_SCROLLING_DEBOUNCE_INTERVAL = 150; @@ -72,6 +116,7 @@ var defaultItemKey = function defaultItemKey(_ref) { var devWarningsOverscanCount = null; +var devWarningsOverscanRowsColumnsCount = null; var devWarningsTagName = null; if (process.env.NODE_ENV !== 'production') { @@ -79,6 +124,9 @@ if (process.env.NODE_ENV !== 'production') { devWarningsOverscanCount = /*#__PURE__*/ new WeakSet(); + devWarningsOverscanRowsColumnsCount = + /*#__PURE__*/ + new WeakSet(); devWarningsTagName = /*#__PURE__*/ new WeakSet(); @@ -183,9 +231,11 @@ function createGridComponent(_ref2) { _this._onScroll = function (event) { var _event$currentTarget = event.currentTarget, + clientHeight = _event$currentTarget.clientHeight, clientWidth = _event$currentTarget.clientWidth, scrollLeft = _event$currentTarget.scrollLeft, scrollTop = _event$currentTarget.scrollTop, + scrollHeight = _event$currentTarget.scrollHeight, scrollWidth = _event$currentTarget.scrollWidth; _this.setState(function (prevState) { @@ -196,25 +246,33 @@ function createGridComponent(_ref2) { return null; } - var direction = _this.props.direction; // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. - // Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). - // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft + var direction = _this.props.direction; // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // It's also easier for this component if we convert offsets to the same format as they would be in for ltr. + // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it. var calculatedScrollLeft = scrollLeft; if (direction === 'rtl') { - if (scrollLeft <= 0) { - calculatedScrollLeft = -scrollLeft; - } else { - calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; + switch (getRTLOffsetType()) { + case 'negative': + calculatedScrollLeft = -scrollLeft; + break; + + case 'positive-descending': + calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; + break; } - } + } // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. + + calculatedScrollLeft = Math.max(0, Math.min(calculatedScrollLeft, scrollWidth - clientWidth)); + var calculatedScrollTop = Math.max(0, Math.min(scrollTop, scrollHeight - clientHeight)); return { isScrolling: true, horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', scrollLeft: calculatedScrollLeft, - scrollTop: scrollTop, + scrollTop: calculatedScrollTop, verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward', scrollUpdateWasRequested: false }; @@ -339,26 +397,55 @@ function createGridComponent(_ref2) { initialScrollLeft = _this$props3.initialScrollLeft, initialScrollTop = _this$props3.initialScrollTop; - if (typeof initialScrollLeft === 'number' && this._outerRef != null) { - this._outerRef.scrollLeft = initialScrollLeft; - } + if (this._outerRef != null) { + var outerRef = this._outerRef; + + if (typeof initialScrollLeft === 'number') { + outerRef.scrollLeft = initialScrollLeft; + } - if (typeof initialScrollTop === 'number' && this._outerRef != null) { - this._outerRef.scrollTop = initialScrollTop; + if (typeof initialScrollTop === 'number') { + outerRef.scrollTop = initialScrollTop; + } } this._callPropsCallbacks(); }; _proto.componentDidUpdate = function componentDidUpdate() { + var direction = this.props.direction; var _this$state2 = this.state, scrollLeft = _this$state2.scrollLeft, scrollTop = _this$state2.scrollTop, scrollUpdateWasRequested = _this$state2.scrollUpdateWasRequested; - if (scrollUpdateWasRequested && this._outerRef !== null) { - this._outerRef.scrollLeft = scrollLeft; - this._outerRef.scrollTop = scrollTop; + if (scrollUpdateWasRequested && this._outerRef != null) { + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // So we need to determine which browser behavior we're dealing with, and mimic it. + var outerRef = this._outerRef; + + if (direction === 'rtl') { + switch (getRTLOffsetType()) { + case 'negative': + outerRef.scrollLeft = -scrollLeft; + break; + + case 'positive-ascending': + outerRef.scrollLeft = scrollLeft; + break; + + default: + var clientWidth = outerRef.clientWidth, + scrollWidth = outerRef.scrollWidth; + outerRef.scrollLeft = scrollWidth - clientWidth - scrollLeft; + break; + } + } else { + outerRef.scrollLeft = Math.max(0, scrollLeft); + } + + outerRef.scrollTop = Math.max(0, scrollTop); } this._callPropsCallbacks(); @@ -442,7 +529,7 @@ function createGridComponent(_ref2) { ref: innerRef, style: { height: estimatedTotalHeight, - pointerEvents: isScrolling ? 'none' : '', + pointerEvents: isScrolling ? 'none' : undefined, width: estimatedTotalWidth } })); @@ -492,6 +579,7 @@ function createGridComponent(_ref2) { _proto._getHorizontalRangeToRender = function _getHorizontalRangeToRender() { var _this$props6 = this.props, columnCount = _this$props6.columnCount, + overscanColumnCount = _this$props6.overscanColumnCount, overscanColumnsCount = _this$props6.overscanColumnsCount, overscanCount = _this$props6.overscanCount, rowCount = _this$props6.rowCount; @@ -499,7 +587,7 @@ function createGridComponent(_ref2) { horizontalScrollDirection = _this$state4.horizontalScrollDirection, isScrolling = _this$state4.isScrolling, scrollLeft = _this$state4.scrollLeft; - var overscanCountResolved = overscanColumnsCount || overscanCount || 1; + var overscanCountResolved = overscanColumnCount || overscanColumnsCount || overscanCount || 1; if (columnCount === 0 || rowCount === 0) { return [0, 0, 0, 0]; @@ -518,13 +606,14 @@ function createGridComponent(_ref2) { var _this$props7 = this.props, columnCount = _this$props7.columnCount, overscanCount = _this$props7.overscanCount, + overscanRowCount = _this$props7.overscanRowCount, overscanRowsCount = _this$props7.overscanRowsCount, rowCount = _this$props7.rowCount; var _this$state5 = this.state, isScrolling = _this$state5.isScrolling, verticalScrollDirection = _this$state5.verticalScrollDirection, scrollTop = _this$state5.scrollTop; - var overscanCountResolved = overscanRowsCount || overscanCount || 1; + var overscanCountResolved = overscanRowCount || overscanRowsCount || overscanCount || 1; if (columnCount === 0 || rowCount === 0) { return [0, 0, 0, 0]; @@ -553,7 +642,9 @@ var validateSharedProps = function validateSharedProps(_ref5, _ref6) { height = _ref5.height, innerTagName = _ref5.innerTagName, outerTagName = _ref5.outerTagName, + overscanColumnsCount = _ref5.overscanColumnsCount, overscanCount = _ref5.overscanCount, + overscanRowsCount = _ref5.overscanRowsCount, width = _ref5.width; var instance = _ref6.instance; @@ -561,7 +652,14 @@ var validateSharedProps = function validateSharedProps(_ref5, _ref6) { if (typeof overscanCount === 'number') { if (devWarningsOverscanCount && !devWarningsOverscanCount.has(instance)) { devWarningsOverscanCount.add(instance); - console.warn('The overscanCount prop has been deprecated. ' + 'Please use the overscanColumnsCount and overscanRowsCount props instead.'); + console.warn('The overscanCount prop has been deprecated. ' + 'Please use the overscanColumnCount and overscanRowCount props instead.'); + } + } + + if (typeof overscanColumnsCount === 'number' || typeof overscanRowsCount === 'number') { + if (devWarningsOverscanRowsColumnsCount && !devWarningsOverscanRowsColumnsCount.has(instance)) { + devWarningsOverscanRowsColumnsCount.add(instance); + console.warn('The overscanColumnsCount and overscanRowsCount props have been deprecated. ' + 'Please use the overscanColumnCount and overscanRowCount props instead.'); } } @@ -770,7 +868,11 @@ var getOffsetForIndexAndAlignment = function getOffsetForIndexAndAlignment(itemT default: if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { return scrollOffset; - } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + } else if (minOffset > maxOffset) { + // Because we only take into account the scrollbar size when calculating minOffset + // this value can be larger than maxOffset when at the end of the list + return minOffset; + } else if (scrollOffset < minOffset) { return minOffset; } else { return maxOffset; @@ -1037,20 +1139,27 @@ function createListComponent(_ref) { return null; } - var direction = _this.props.direction; // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. - // Chrome does not seem to adhere; its scrolLeft values are positive (measured relative to the left). - // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft - + var direction = _this.props.direction; var scrollOffset = scrollLeft; if (direction === 'rtl') { - if (scrollLeft <= 0) { - scrollOffset = -scrollOffset; - } else { - scrollOffset = scrollWidth - clientWidth - scrollLeft; + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // It's also easier for this component if we convert offsets to the same format as they would be in for ltr. + // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it. + switch (getRTLOffsetType()) { + case 'negative': + scrollOffset = -scrollLeft; + break; + + case 'positive-descending': + scrollOffset = scrollWidth - clientWidth - scrollLeft; + break; } - } + } // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. + + scrollOffset = Math.max(0, Math.min(scrollOffset, scrollWidth - clientWidth)); return { isScrolling: true, scrollDirection: prevState.scrollOffset < scrollLeft ? 'forward' : 'backward', @@ -1061,7 +1170,10 @@ function createListComponent(_ref) { }; _this._onScrollVertical = function (event) { - var scrollTop = event.currentTarget.scrollTop; + var _event$currentTarget2 = event.currentTarget, + clientHeight = _event$currentTarget2.clientHeight, + scrollHeight = _event$currentTarget2.scrollHeight, + scrollTop = _event$currentTarget2.scrollTop; _this.setState(function (prevState) { if (prevState.scrollOffset === scrollTop) { @@ -1069,12 +1181,14 @@ function createListComponent(_ref) { // In which case we don't need to trigger another render, // And we don't want to update state.isScrolling. return null; - } + } // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. + + var scrollOffset = Math.max(0, Math.min(scrollTop, scrollHeight - clientHeight)); return { isScrolling: true, - scrollDirection: prevState.scrollOffset < scrollTop ? 'forward' : 'backward', - scrollOffset: scrollTop, + scrollDirection: prevState.scrollOffset < scrollOffset ? 'forward' : 'backward', + scrollOffset: scrollOffset, scrollUpdateWasRequested: false }; }, _this._resetIsScrollingDebounced); @@ -1154,12 +1268,13 @@ function createListComponent(_ref) { initialScrollOffset = _this$props2.initialScrollOffset, layout = _this$props2.layout; - if (typeof initialScrollOffset === 'number' && this._outerRef !== null) { - // TODO Deprecate direction "horizontal" + if (typeof initialScrollOffset === 'number' && this._outerRef != null) { + var outerRef = this._outerRef; // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { - this._outerRef.scrollLeft = initialScrollOffset; + outerRef.scrollLeft = initialScrollOffset; } else { - this._outerRef.scrollTop = initialScrollOffset; + outerRef.scrollTop = initialScrollOffset; } } @@ -1174,12 +1289,34 @@ function createListComponent(_ref) { scrollOffset = _this$state.scrollOffset, scrollUpdateWasRequested = _this$state.scrollUpdateWasRequested; - if (scrollUpdateWasRequested && this._outerRef !== null) { - // TODO Deprecate direction "horizontal" + if (scrollUpdateWasRequested && this._outerRef != null) { + var outerRef = this._outerRef; // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { - this._outerRef.scrollLeft = scrollOffset; + if (direction === 'rtl') { + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // So we need to determine which browser behavior we're dealing with, and mimic it. + switch (getRTLOffsetType()) { + case 'negative': + outerRef.scrollLeft = -scrollOffset; + break; + + case 'positive-ascending': + outerRef.scrollLeft = scrollOffset; + break; + + default: + var clientWidth = outerRef.clientWidth, + scrollWidth = outerRef.scrollWidth; + outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset; + break; + } + } else { + outerRef.scrollLeft = scrollOffset; + } } else { - this._outerRef.scrollTop = scrollOffset; + outerRef.scrollTop = scrollOffset; } } @@ -1255,7 +1392,7 @@ function createListComponent(_ref) { ref: innerRef, style: { height: isHorizontal ? '100%' : estimatedTotalSize, - pointerEvents: isScrolling ? 'none' : '', + pointerEvents: isScrolling ? 'none' : undefined, width: isHorizontal ? estimatedTotalSize : '100%' } })); @@ -1537,7 +1674,7 @@ createListComponent({ default: if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { return scrollOffset; - } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + } else if (scrollOffset < minOffset) { return minOffset; } else { return maxOffset; @@ -1642,7 +1779,8 @@ createGridComponent({ var columnCount = _ref7.columnCount, columnWidth = _ref7.columnWidth, width = _ref7.width; - var maxOffset = Math.max(0, Math.min(columnCount * columnWidth - width, columnIndex * columnWidth)); + var lastColumnOffset = Math.max(0, columnCount * columnWidth - width); + var maxOffset = Math.min(lastColumnOffset, columnIndex * columnWidth); var minOffset = Math.max(0, columnIndex * columnWidth - width + scrollbarSize + columnWidth); if (align === 'smart') { @@ -1661,13 +1799,27 @@ createGridComponent({ return minOffset; case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); + // "Centered" offset is usually the average of the min and max. + // But near the edges of the list, this doesn't hold true. + var middleOffset = Math.round(minOffset + (maxOffset - minOffset) / 2); + + if (middleOffset < Math.ceil(width / 2)) { + return 0; // near the beginning + } else if (middleOffset > lastColumnOffset + Math.floor(width / 2)) { + return lastColumnOffset; // near the end + } else { + return middleOffset; + } case 'auto': default: if (scrollLeft >= minOffset && scrollLeft <= maxOffset) { return scrollLeft; - } else if (scrollLeft - minOffset < maxOffset - scrollLeft) { + } else if (minOffset > maxOffset) { + // Because we only take into account the scrollbar size when calculating minOffset + // this value can be larger than maxOffset when at the end of the list + return minOffset; + } else if (scrollLeft < minOffset) { return minOffset; } else { return maxOffset; @@ -1679,7 +1831,8 @@ createGridComponent({ var rowHeight = _ref8.rowHeight, height = _ref8.height, rowCount = _ref8.rowCount; - var maxOffset = Math.max(0, Math.min(rowCount * rowHeight - height, rowIndex * rowHeight)); + var lastRowOffset = Math.max(0, rowCount * rowHeight - height); + var maxOffset = Math.min(lastRowOffset, rowIndex * rowHeight); var minOffset = Math.max(0, rowIndex * rowHeight - height + scrollbarSize + rowHeight); if (align === 'smart') { @@ -1698,13 +1851,27 @@ createGridComponent({ return minOffset; case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); + // "Centered" offset is usually the average of the min and max. + // But near the edges of the list, this doesn't hold true. + var middleOffset = Math.round(minOffset + (maxOffset - minOffset) / 2); + + if (middleOffset < Math.ceil(height / 2)) { + return 0; // near the beginning + } else if (middleOffset > lastRowOffset + Math.floor(height / 2)) { + return lastRowOffset; // near the end + } else { + return middleOffset; + } case 'auto': default: if (scrollTop >= minOffset && scrollTop <= maxOffset) { return scrollTop; - } else if (scrollTop - minOffset < maxOffset - scrollTop) { + } else if (minOffset > maxOffset) { + // Because we only take into account the scrollbar size when calculating minOffset + // this value can be larger than maxOffset when at the end of the list + return minOffset; + } else if (scrollTop < minOffset) { return minOffset; } else { return maxOffset; @@ -1722,7 +1889,9 @@ createGridComponent({ columnCount = _ref10.columnCount, width = _ref10.width; var left = startIndex * columnWidth; - return Math.max(0, Math.min(columnCount - 1, startIndex + Math.floor((width + (scrollLeft - left)) / columnWidth))); + var numVisibleColumns = Math.ceil((width + scrollLeft - left) / columnWidth); + return Math.max(0, Math.min(columnCount - 1, startIndex + numVisibleColumns - 1 // -1 is because stop index is inclusive + )); }, getRowStartIndexForOffset: function getRowStartIndexForOffset(_ref11, scrollTop) { var rowHeight = _ref11.rowHeight, @@ -1733,8 +1902,10 @@ createGridComponent({ var rowHeight = _ref12.rowHeight, rowCount = _ref12.rowCount, height = _ref12.height; - var left = startIndex * rowHeight; - return Math.max(0, Math.min(rowCount - 1, startIndex + Math.floor((height + (scrollTop - left)) / rowHeight))); + var top = startIndex * rowHeight; + var numVisibleRows = Math.ceil((height + scrollTop - top) / rowHeight); + return Math.max(0, Math.min(rowCount - 1, startIndex + numVisibleRows - 1 // -1 is because stop index is inclusive + )); }, initInstanceProps: function initInstanceProps(props) {// Noop }, @@ -1759,13 +1930,11 @@ var FixedSizeList = /*#__PURE__*/ createListComponent({ getItemOffset: function getItemOffset(_ref, index) { - var itemSize = _ref.itemSize, - size = _ref.size; + var itemSize = _ref.itemSize; return index * itemSize; }, getItemSize: function getItemSize(_ref2, index) { - var itemSize = _ref2.itemSize, - size = _ref2.size; + var itemSize = _ref2.itemSize; return itemSize; }, getEstimatedTotalSize: function getEstimatedTotalSize(_ref3) { @@ -1783,7 +1952,8 @@ createListComponent({ // TODO Deprecate direction "horizontal" var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; var size = isHorizontal ? width : height; - var maxOffset = Math.max(0, Math.min(itemCount * itemSize - size, index * itemSize)); + var lastItemOffset = Math.max(0, itemCount * itemSize - size); + var maxOffset = Math.min(lastItemOffset, index * itemSize); var minOffset = Math.max(0, index * itemSize - size + itemSize); if (align === 'smart') { @@ -1802,13 +1972,25 @@ createListComponent({ return minOffset; case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); + { + // "Centered" offset is usually the average of the min and max. + // But near the edges of the list, this doesn't hold true. + var middleOffset = Math.round(minOffset + (maxOffset - minOffset) / 2); + + if (middleOffset < Math.ceil(size / 2)) { + return 0; // near the beginning + } else if (middleOffset > lastItemOffset + Math.floor(size / 2)) { + return lastItemOffset; // near the end + } else { + return middleOffset; + } + } case 'auto': default: if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { return scrollOffset; - } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + } else if (scrollOffset < minOffset) { return minOffset; } else { return maxOffset; @@ -1832,7 +2014,9 @@ createListComponent({ var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; var offset = startIndex * itemSize; var size = isHorizontal ? width : height; - return Math.max(0, Math.min(itemCount - 1, startIndex + Math.floor((size + (scrollOffset - offset)) / itemSize))); + var numVisibleItems = Math.ceil((size + scrollOffset - offset) / itemSize); + return Math.max(0, Math.min(itemCount - 1, startIndex + numVisibleItems - 1 // -1 is because stop index is inclusive + )); }, initInstanceProps: function initInstanceProps(props) {// Noop }, @@ -1892,3 +2076,4 @@ exports.FixedSizeGrid = FixedSizeGrid; exports.FixedSizeList = FixedSizeList; exports.areEqual = areEqual; exports.shouldComponentUpdate = shouldComponentUpdate; +//# sourceMappingURL=index.cjs.js.map diff --git a/vendor/react-window/dist/index.esm.js b/vendor/react-window/dist/index.esm.js index 19b1884d12ca..9cf49546c891 100644 --- a/vendor/react-window/dist/index.esm.js +++ b/vendor/react-window/dist/index.esm.js @@ -54,6 +54,50 @@ function getScrollbarSize(recalculate) { return size; } +var cachedRTLResult = null; // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. +// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). +// Safari's elastic bounce makes detecting this even more complicated wrt potential false positives. +// The safest way to check this is to intentionally set a negative offset, +// and then verify that the subsequent "scroll" event matches the negative offset. +// If it does not match, then we can assume a non-standard RTL scroll implementation. + +function getRTLOffsetType(recalculate) { + if (recalculate === void 0) { + recalculate = false; + } + + if (cachedRTLResult === null || recalculate) { + var outerDiv = document.createElement('div'); + var outerStyle = outerDiv.style; + outerStyle.width = '50px'; + outerStyle.height = '50px'; + outerStyle.overflow = 'scroll'; + outerStyle.direction = 'rtl'; + var innerDiv = document.createElement('div'); + var innerStyle = innerDiv.style; + innerStyle.width = '100px'; + innerStyle.height = '100px'; + outerDiv.appendChild(innerDiv); + document.body.appendChild(outerDiv); + + if (outerDiv.scrollLeft > 0) { + cachedRTLResult = 'positive-descending'; + } else { + outerDiv.scrollLeft = 1; + + if (outerDiv.scrollLeft === 0) { + cachedRTLResult = 'negative'; + } else { + cachedRTLResult = 'positive-ascending'; + } + } + + document.body.removeChild(outerDiv); + return cachedRTLResult; + } + + return cachedRTLResult; +} var IS_SCROLLING_DEBOUNCE_INTERVAL = 150; @@ -67,6 +111,7 @@ var defaultItemKey = function defaultItemKey(_ref) { var devWarningsOverscanCount = null; +var devWarningsOverscanRowsColumnsCount = null; var devWarningsTagName = null; if (process.env.NODE_ENV !== 'production') { @@ -74,6 +119,9 @@ if (process.env.NODE_ENV !== 'production') { devWarningsOverscanCount = /*#__PURE__*/ new WeakSet(); + devWarningsOverscanRowsColumnsCount = + /*#__PURE__*/ + new WeakSet(); devWarningsTagName = /*#__PURE__*/ new WeakSet(); @@ -178,9 +226,11 @@ function createGridComponent(_ref2) { _this._onScroll = function (event) { var _event$currentTarget = event.currentTarget, + clientHeight = _event$currentTarget.clientHeight, clientWidth = _event$currentTarget.clientWidth, scrollLeft = _event$currentTarget.scrollLeft, scrollTop = _event$currentTarget.scrollTop, + scrollHeight = _event$currentTarget.scrollHeight, scrollWidth = _event$currentTarget.scrollWidth; // Force flush sync for scroll updates to reduce visual checkerboarding. @@ -193,25 +243,33 @@ function createGridComponent(_ref2) { return null; } - var direction = _this.props.direction; // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. - // Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). - // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft + var direction = _this.props.direction; // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // It's also easier for this component if we convert offsets to the same format as they would be in for ltr. + // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it. var calculatedScrollLeft = scrollLeft; if (direction === 'rtl') { - if (scrollLeft <= 0) { - calculatedScrollLeft = -scrollLeft; - } else { - calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; + switch (getRTLOffsetType()) { + case 'negative': + calculatedScrollLeft = -scrollLeft; + break; + + case 'positive-descending': + calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; + break; } - } + } // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. + + calculatedScrollLeft = Math.max(0, Math.min(calculatedScrollLeft, scrollWidth - clientWidth)); + var calculatedScrollTop = Math.max(0, Math.min(scrollTop, scrollHeight - clientHeight)); return { isScrolling: true, horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', scrollLeft: calculatedScrollLeft, - scrollTop: scrollTop, + scrollTop: calculatedScrollTop, verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward', scrollUpdateWasRequested: false }; @@ -337,26 +395,55 @@ function createGridComponent(_ref2) { initialScrollLeft = _this$props3.initialScrollLeft, initialScrollTop = _this$props3.initialScrollTop; - if (typeof initialScrollLeft === 'number' && this._outerRef != null) { - this._outerRef.scrollLeft = initialScrollLeft; - } + if (this._outerRef != null) { + var outerRef = this._outerRef; - if (typeof initialScrollTop === 'number' && this._outerRef != null) { - this._outerRef.scrollTop = initialScrollTop; + if (typeof initialScrollLeft === 'number') { + outerRef.scrollLeft = initialScrollLeft; + } + + if (typeof initialScrollTop === 'number') { + outerRef.scrollTop = initialScrollTop; + } } this._callPropsCallbacks(); }; _proto.componentDidUpdate = function componentDidUpdate() { + var direction = this.props.direction; var _this$state2 = this.state, scrollLeft = _this$state2.scrollLeft, scrollTop = _this$state2.scrollTop, scrollUpdateWasRequested = _this$state2.scrollUpdateWasRequested; - if (scrollUpdateWasRequested && this._outerRef !== null) { - this._outerRef.scrollLeft = scrollLeft; - this._outerRef.scrollTop = scrollTop; + if (scrollUpdateWasRequested && this._outerRef != null) { + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // So we need to determine which browser behavior we're dealing with, and mimic it. + var outerRef = this._outerRef; + + if (direction === 'rtl') { + switch (getRTLOffsetType()) { + case 'negative': + outerRef.scrollLeft = -scrollLeft; + break; + + case 'positive-ascending': + outerRef.scrollLeft = scrollLeft; + break; + + default: + var clientWidth = outerRef.clientWidth, + scrollWidth = outerRef.scrollWidth; + outerRef.scrollLeft = scrollWidth - clientWidth - scrollLeft; + break; + } + } else { + outerRef.scrollLeft = Math.max(0, scrollLeft); + } + + outerRef.scrollTop = Math.max(0, scrollTop); } this._callPropsCallbacks(); @@ -440,7 +527,7 @@ function createGridComponent(_ref2) { ref: innerRef, style: { height: estimatedTotalHeight, - pointerEvents: isScrolling ? 'none' : '', + pointerEvents: isScrolling ? 'none' : undefined, width: estimatedTotalWidth } })); @@ -490,6 +577,7 @@ function createGridComponent(_ref2) { _proto._getHorizontalRangeToRender = function _getHorizontalRangeToRender() { var _this$props6 = this.props, columnCount = _this$props6.columnCount, + overscanColumnCount = _this$props6.overscanColumnCount, overscanColumnsCount = _this$props6.overscanColumnsCount, overscanCount = _this$props6.overscanCount, rowCount = _this$props6.rowCount; @@ -497,7 +585,7 @@ function createGridComponent(_ref2) { horizontalScrollDirection = _this$state4.horizontalScrollDirection, isScrolling = _this$state4.isScrolling, scrollLeft = _this$state4.scrollLeft; - var overscanCountResolved = overscanColumnsCount || overscanCount || 1; + var overscanCountResolved = overscanColumnCount || overscanColumnsCount || overscanCount || 1; if (columnCount === 0 || rowCount === 0) { return [0, 0, 0, 0]; @@ -516,13 +604,14 @@ function createGridComponent(_ref2) { var _this$props7 = this.props, columnCount = _this$props7.columnCount, overscanCount = _this$props7.overscanCount, + overscanRowCount = _this$props7.overscanRowCount, overscanRowsCount = _this$props7.overscanRowsCount, rowCount = _this$props7.rowCount; var _this$state5 = this.state, isScrolling = _this$state5.isScrolling, verticalScrollDirection = _this$state5.verticalScrollDirection, scrollTop = _this$state5.scrollTop; - var overscanCountResolved = overscanRowsCount || overscanCount || 1; + var overscanCountResolved = overscanRowCount || overscanRowsCount || overscanCount || 1; if (columnCount === 0 || rowCount === 0) { return [0, 0, 0, 0]; @@ -551,7 +640,9 @@ var validateSharedProps = function validateSharedProps(_ref5, _ref6) { height = _ref5.height, innerTagName = _ref5.innerTagName, outerTagName = _ref5.outerTagName, + overscanColumnsCount = _ref5.overscanColumnsCount, overscanCount = _ref5.overscanCount, + overscanRowsCount = _ref5.overscanRowsCount, width = _ref5.width; var instance = _ref6.instance; @@ -559,7 +650,14 @@ var validateSharedProps = function validateSharedProps(_ref5, _ref6) { if (typeof overscanCount === 'number') { if (devWarningsOverscanCount && !devWarningsOverscanCount.has(instance)) { devWarningsOverscanCount.add(instance); - console.warn('The overscanCount prop has been deprecated. ' + 'Please use the overscanColumnsCount and overscanRowsCount props instead.'); + console.warn('The overscanCount prop has been deprecated. ' + 'Please use the overscanColumnCount and overscanRowCount props instead.'); + } + } + + if (typeof overscanColumnsCount === 'number' || typeof overscanRowsCount === 'number') { + if (devWarningsOverscanRowsColumnsCount && !devWarningsOverscanRowsColumnsCount.has(instance)) { + devWarningsOverscanRowsColumnsCount.add(instance); + console.warn('The overscanColumnsCount and overscanRowsCount props have been deprecated. ' + 'Please use the overscanColumnCount and overscanRowCount props instead.'); } } @@ -768,7 +866,11 @@ var getOffsetForIndexAndAlignment = function getOffsetForIndexAndAlignment(itemT default: if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { return scrollOffset; - } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + } else if (minOffset > maxOffset) { + // Because we only take into account the scrollbar size when calculating minOffset + // this value can be larger than maxOffset when at the end of the list + return minOffset; + } else if (scrollOffset < minOffset) { return minOffset; } else { return maxOffset; @@ -1037,20 +1139,27 @@ function createListComponent(_ref) { return null; } - var direction = _this.props.direction; // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. - // Chrome does not seem to adhere; its scrolLeft values are positive (measured relative to the left). - // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft - + var direction = _this.props.direction; var scrollOffset = scrollLeft; if (direction === 'rtl') { - if (scrollLeft <= 0) { - scrollOffset = -scrollOffset; - } else { - scrollOffset = scrollWidth - clientWidth - scrollLeft; + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // It's also easier for this component if we convert offsets to the same format as they would be in for ltr. + // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it. + switch (getRTLOffsetType()) { + case 'negative': + scrollOffset = -scrollLeft; + break; + + case 'positive-descending': + scrollOffset = scrollWidth - clientWidth - scrollLeft; + break; } - } + } // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. + + scrollOffset = Math.max(0, Math.min(scrollOffset, scrollWidth - clientWidth)); return { isScrolling: true, scrollDirection: prevState.scrollOffset < scrollLeft ? 'forward' : 'backward', @@ -1058,11 +1167,14 @@ function createListComponent(_ref) { scrollUpdateWasRequested: false }; }, _this._resetIsScrollingDebounced); - }); - }; + }; + }); _this._onScrollVertical = function (event) { - var scrollTop = event.currentTarget.scrollTop; + var _event$currentTarget2 = event.currentTarget, + clientHeight = _event$currentTarget2.clientHeight, + scrollHeight = _event$currentTarget2.scrollHeight, + scrollTop = _event$currentTarget2.scrollTop; // Force flush sync for scroll updates to reduce visual checkerboarding. flushSync(() => { @@ -1072,17 +1184,19 @@ function createListComponent(_ref) { // In which case we don't need to trigger another render, // And we don't want to update state.isScrolling. return null; - } + } // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. + + var scrollOffset = Math.max(0, Math.min(scrollTop, scrollHeight - clientHeight)); return { isScrolling: true, - scrollDirection: prevState.scrollOffset < scrollTop ? 'forward' : 'backward', - scrollOffset: scrollTop, + scrollDirection: prevState.scrollOffset < scrollOffset ? 'forward' : 'backward', + scrollOffset: scrollOffset, scrollUpdateWasRequested: false }; }, _this._resetIsScrollingDebounced); - }); - }; + }; + }); _this._outerRefSetter = function (ref) { var outerRef = _this.props.outerRef; @@ -1158,12 +1272,13 @@ function createListComponent(_ref) { initialScrollOffset = _this$props2.initialScrollOffset, layout = _this$props2.layout; - if (typeof initialScrollOffset === 'number' && this._outerRef !== null) { - // TODO Deprecate direction "horizontal" + if (typeof initialScrollOffset === 'number' && this._outerRef != null) { + var outerRef = this._outerRef; // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { - this._outerRef.scrollLeft = initialScrollOffset; + outerRef.scrollLeft = initialScrollOffset; } else { - this._outerRef.scrollTop = initialScrollOffset; + outerRef.scrollTop = initialScrollOffset; } } @@ -1178,12 +1293,34 @@ function createListComponent(_ref) { scrollOffset = _this$state.scrollOffset, scrollUpdateWasRequested = _this$state.scrollUpdateWasRequested; - if (scrollUpdateWasRequested && this._outerRef !== null) { - // TODO Deprecate direction "horizontal" + if (scrollUpdateWasRequested && this._outerRef != null) { + var outerRef = this._outerRef; // TODO Deprecate direction "horizontal" + if (direction === 'horizontal' || layout === 'horizontal') { - this._outerRef.scrollLeft = scrollOffset; + if (direction === 'rtl') { + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // So we need to determine which browser behavior we're dealing with, and mimic it. + switch (getRTLOffsetType()) { + case 'negative': + outerRef.scrollLeft = -scrollOffset; + break; + + case 'positive-ascending': + outerRef.scrollLeft = scrollOffset; + break; + + default: + var clientWidth = outerRef.clientWidth, + scrollWidth = outerRef.scrollWidth; + outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset; + break; + } + } else { + outerRef.scrollLeft = scrollOffset; + } } else { - this._outerRef.scrollTop = scrollOffset; + outerRef.scrollTop = scrollOffset; } } @@ -1259,7 +1396,7 @@ function createListComponent(_ref) { ref: innerRef, style: { height: isHorizontal ? '100%' : estimatedTotalSize, - pointerEvents: isScrolling ? 'none' : '', + pointerEvents: isScrolling ? 'none' : undefined, width: isHorizontal ? estimatedTotalSize : '100%' } })); @@ -1541,7 +1678,7 @@ createListComponent({ default: if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { return scrollOffset; - } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + } else if (scrollOffset < minOffset) { return minOffset; } else { return maxOffset; @@ -1646,7 +1783,8 @@ createGridComponent({ var columnCount = _ref7.columnCount, columnWidth = _ref7.columnWidth, width = _ref7.width; - var maxOffset = Math.max(0, Math.min(columnCount * columnWidth - width, columnIndex * columnWidth)); + var lastColumnOffset = Math.max(0, columnCount * columnWidth - width); + var maxOffset = Math.min(lastColumnOffset, columnIndex * columnWidth); var minOffset = Math.max(0, columnIndex * columnWidth - width + scrollbarSize + columnWidth); if (align === 'smart') { @@ -1665,13 +1803,27 @@ createGridComponent({ return minOffset; case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); + // "Centered" offset is usually the average of the min and max. + // But near the edges of the list, this doesn't hold true. + var middleOffset = Math.round(minOffset + (maxOffset - minOffset) / 2); + + if (middleOffset < Math.ceil(width / 2)) { + return 0; // near the beginning + } else if (middleOffset > lastColumnOffset + Math.floor(width / 2)) { + return lastColumnOffset; // near the end + } else { + return middleOffset; + } case 'auto': default: if (scrollLeft >= minOffset && scrollLeft <= maxOffset) { return scrollLeft; - } else if (scrollLeft - minOffset < maxOffset - scrollLeft) { + } else if (minOffset > maxOffset) { + // Because we only take into account the scrollbar size when calculating minOffset + // this value can be larger than maxOffset when at the end of the list + return minOffset; + } else if (scrollLeft < minOffset) { return minOffset; } else { return maxOffset; @@ -1683,7 +1835,8 @@ createGridComponent({ var rowHeight = _ref8.rowHeight, height = _ref8.height, rowCount = _ref8.rowCount; - var maxOffset = Math.max(0, Math.min(rowCount * rowHeight - height, rowIndex * rowHeight)); + var lastRowOffset = Math.max(0, rowCount * rowHeight - height); + var maxOffset = Math.min(lastRowOffset, rowIndex * rowHeight); var minOffset = Math.max(0, rowIndex * rowHeight - height + scrollbarSize + rowHeight); if (align === 'smart') { @@ -1702,13 +1855,27 @@ createGridComponent({ return minOffset; case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); + // "Centered" offset is usually the average of the min and max. + // But near the edges of the list, this doesn't hold true. + var middleOffset = Math.round(minOffset + (maxOffset - minOffset) / 2); + + if (middleOffset < Math.ceil(height / 2)) { + return 0; // near the beginning + } else if (middleOffset > lastRowOffset + Math.floor(height / 2)) { + return lastRowOffset; // near the end + } else { + return middleOffset; + } case 'auto': default: if (scrollTop >= minOffset && scrollTop <= maxOffset) { return scrollTop; - } else if (scrollTop - minOffset < maxOffset - scrollTop) { + } else if (minOffset > maxOffset) { + // Because we only take into account the scrollbar size when calculating minOffset + // this value can be larger than maxOffset when at the end of the list + return minOffset; + } else if (scrollTop < minOffset) { return minOffset; } else { return maxOffset; @@ -1726,7 +1893,9 @@ createGridComponent({ columnCount = _ref10.columnCount, width = _ref10.width; var left = startIndex * columnWidth; - return Math.max(0, Math.min(columnCount - 1, startIndex + Math.floor((width + (scrollLeft - left)) / columnWidth))); + var numVisibleColumns = Math.ceil((width + scrollLeft - left) / columnWidth); + return Math.max(0, Math.min(columnCount - 1, startIndex + numVisibleColumns - 1 // -1 is because stop index is inclusive + )); }, getRowStartIndexForOffset: function getRowStartIndexForOffset(_ref11, scrollTop) { var rowHeight = _ref11.rowHeight, @@ -1737,8 +1906,10 @@ createGridComponent({ var rowHeight = _ref12.rowHeight, rowCount = _ref12.rowCount, height = _ref12.height; - var left = startIndex * rowHeight; - return Math.max(0, Math.min(rowCount - 1, startIndex + Math.floor((height + (scrollTop - left)) / rowHeight))); + var top = startIndex * rowHeight; + var numVisibleRows = Math.ceil((height + scrollTop - top) / rowHeight); + return Math.max(0, Math.min(rowCount - 1, startIndex + numVisibleRows - 1 // -1 is because stop index is inclusive + )); }, initInstanceProps: function initInstanceProps(props) {// Noop }, @@ -1763,13 +1934,11 @@ var FixedSizeList = /*#__PURE__*/ createListComponent({ getItemOffset: function getItemOffset(_ref, index) { - var itemSize = _ref.itemSize, - size = _ref.size; + var itemSize = _ref.itemSize; return index * itemSize; }, getItemSize: function getItemSize(_ref2, index) { - var itemSize = _ref2.itemSize, - size = _ref2.size; + var itemSize = _ref2.itemSize; return itemSize; }, getEstimatedTotalSize: function getEstimatedTotalSize(_ref3) { @@ -1787,7 +1956,8 @@ createListComponent({ // TODO Deprecate direction "horizontal" var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; var size = isHorizontal ? width : height; - var maxOffset = Math.max(0, Math.min(itemCount * itemSize - size, index * itemSize)); + var lastItemOffset = Math.max(0, itemCount * itemSize - size); + var maxOffset = Math.min(lastItemOffset, index * itemSize); var minOffset = Math.max(0, index * itemSize - size + itemSize); if (align === 'smart') { @@ -1806,13 +1976,25 @@ createListComponent({ return minOffset; case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); + { + // "Centered" offset is usually the average of the min and max. + // But near the edges of the list, this doesn't hold true. + var middleOffset = Math.round(minOffset + (maxOffset - minOffset) / 2); + + if (middleOffset < Math.ceil(size / 2)) { + return 0; // near the beginning + } else if (middleOffset > lastItemOffset + Math.floor(size / 2)) { + return lastItemOffset; // near the end + } else { + return middleOffset; + } + } case 'auto': default: if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { return scrollOffset; - } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + } else if (scrollOffset < minOffset) { return minOffset; } else { return maxOffset; @@ -1836,7 +2018,9 @@ createListComponent({ var isHorizontal = direction === 'horizontal' || layout === 'horizontal'; var offset = startIndex * itemSize; var size = isHorizontal ? width : height; - return Math.max(0, Math.min(itemCount - 1, startIndex + Math.floor((size + (scrollOffset - offset)) / itemSize))); + var numVisibleItems = Math.ceil((size + scrollOffset - offset) / itemSize); + return Math.max(0, Math.min(itemCount - 1, startIndex + numVisibleItems - 1 // -1 is because stop index is inclusive + )); }, initInstanceProps: function initInstanceProps(props) {// Noop }, @@ -1891,3 +2075,4 @@ function shouldComponentUpdate(nextProps, nextState) { } export { VariableSizeGrid, VariableSizeList, FixedSizeGrid, FixedSizeList, areEqual, shouldComponentUpdate }; +//# sourceMappingURL=index.esm.js.map diff --git a/vendor/react-window/package.json b/vendor/react-window/package.json index adaea80fdb37..d10a25a04ffd 100644 --- a/vendor/react-window/package.json +++ b/vendor/react-window/package.json @@ -1,6 +1,6 @@ { "name": "react-window", - "version": "1.8.0", + "version": "1.8.5", "description": "React components for efficiently rendering large, scrollable lists and tabular data", "author": @@ -91,7 +91,7 @@ "eslint-plugin-promise": "^3.7.0", "eslint-plugin-react": "^7.7.0", "eslint-plugin-standard": "^3.0.1", - "flow-bin": "^0.80.0", + "flow-bin": "^0.103.0", "gh-pages": "^1.1.0", "lint-staged": "^7.0.5", "prettier": "^1.12.1", @@ -103,6 +103,8 @@ "rollup": "^1.4.1", "rollup-plugin-babel": "^4.3.2", "rollup-plugin-commonjs": "^9.2.1", - "rollup-plugin-node-resolve": "^4.0.1" + "rollup-plugin-node-resolve": "^4.0.1", + "rollup-plugin-replace": "^2.2.0", + "rollup-plugin-terser": "^5.1.0" } } diff --git a/vendor/react-window/src/FixedSizeGrid.js b/vendor/react-window/src/FixedSizeGrid.js index 452536a9df4b..685ad3347820 100644 --- a/vendor/react-window/src/FixedSizeGrid.js +++ b/vendor/react-window/src/FixedSizeGrid.js @@ -31,12 +31,13 @@ const FixedSizeGrid = createGridComponent({ instanceProps: typeof undefined, scrollbarSize: number ): number => { - const maxOffset = Math.max( + const lastColumnOffset = Math.max( 0, - Math.min( - columnCount * ((columnWidth: any): number) - width, - columnIndex * ((columnWidth: any): number) - ) + columnCount * ((columnWidth: any): number) - width + ); + const maxOffset = Math.min( + lastColumnOffset, + columnIndex * ((columnWidth: any): number) ); const minOffset = Math.max( 0, @@ -60,12 +61,27 @@ const FixedSizeGrid = createGridComponent({ case 'end': return minOffset; case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); + // "Centered" offset is usually the average of the min and max. + // But near the edges of the list, this doesn't hold true. + const middleOffset = Math.round( + minOffset + (maxOffset - minOffset) / 2 + ); + if (middleOffset < Math.ceil(width / 2)) { + return 0; // near the beginning + } else if (middleOffset > lastColumnOffset + Math.floor(width / 2)) { + return lastColumnOffset; // near the end + } else { + return middleOffset; + } case 'auto': default: if (scrollLeft >= minOffset && scrollLeft <= maxOffset) { return scrollLeft; - } else if (scrollLeft - minOffset < maxOffset - scrollLeft) { + } else if (minOffset > maxOffset) { + // Because we only take into account the scrollbar size when calculating minOffset + // this value can be larger than maxOffset when at the end of the list + return minOffset; + } else if (scrollLeft < minOffset) { return minOffset; } else { return maxOffset; @@ -81,12 +97,13 @@ const FixedSizeGrid = createGridComponent({ instanceProps: typeof undefined, scrollbarSize: number ): number => { - const maxOffset = Math.max( + const lastRowOffset = Math.max( 0, - Math.min( - rowCount * ((rowHeight: any): number) - height, - rowIndex * ((rowHeight: any): number) - ) + rowCount * ((rowHeight: any): number) - height + ); + const maxOffset = Math.min( + lastRowOffset, + rowIndex * ((rowHeight: any): number) ); const minOffset = Math.max( 0, @@ -110,12 +127,27 @@ const FixedSizeGrid = createGridComponent({ case 'end': return minOffset; case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); + // "Centered" offset is usually the average of the min and max. + // But near the edges of the list, this doesn't hold true. + const middleOffset = Math.round( + minOffset + (maxOffset - minOffset) / 2 + ); + if (middleOffset < Math.ceil(height / 2)) { + return 0; // near the beginning + } else if (middleOffset > lastRowOffset + Math.floor(height / 2)) { + return lastRowOffset; // near the end + } else { + return middleOffset; + } case 'auto': default: if (scrollTop >= minOffset && scrollTop <= maxOffset) { return scrollTop; - } else if (scrollTop - minOffset < maxOffset - scrollTop) { + } else if (minOffset > maxOffset) { + // Because we only take into account the scrollbar size when calculating minOffset + // this value can be larger than maxOffset when at the end of the list + return minOffset; + } else if (scrollTop < minOffset) { return minOffset; } else { return maxOffset; @@ -141,14 +173,14 @@ const FixedSizeGrid = createGridComponent({ scrollLeft: number ): number => { const left = startIndex * ((columnWidth: any): number); + const numVisibleColumns = Math.ceil( + (width + scrollLeft - left) / ((columnWidth: any): number) + ); return Math.max( 0, Math.min( columnCount - 1, - startIndex + - Math.floor( - (width + (scrollLeft - left)) / ((columnWidth: any): number) - ) + startIndex + numVisibleColumns - 1 // -1 is because stop index is inclusive ) ); }, @@ -167,13 +199,15 @@ const FixedSizeGrid = createGridComponent({ startIndex: number, scrollTop: number ): number => { - const left = startIndex * ((rowHeight: any): number); + const top = startIndex * ((rowHeight: any): number); + const numVisibleRows = Math.ceil( + (height + scrollTop - top) / ((rowHeight: any): number) + ); return Math.max( 0, Math.min( rowCount - 1, - startIndex + - Math.floor((height + (scrollTop - left)) / ((rowHeight: any): number)) + startIndex + numVisibleRows - 1 // -1 is because stop index is inclusive ) ); }, diff --git a/vendor/react-window/src/FixedSizeList.js b/vendor/react-window/src/FixedSizeList.js index 991f9004305b..0e0fc305bc30 100644 --- a/vendor/react-window/src/FixedSizeList.js +++ b/vendor/react-window/src/FixedSizeList.js @@ -5,10 +5,10 @@ import createListComponent from './createListComponent'; import type { Props, ScrollToAlign } from './createListComponent'; const FixedSizeList = createListComponent({ - getItemOffset: ({ itemSize, size }: Props, index: number): number => + getItemOffset: ({ itemSize }: Props, index: number): number => index * ((itemSize: any): number), - getItemSize: ({ itemSize, size }: Props, index: number): number => + getItemSize: ({ itemSize }: Props, index: number): number => ((itemSize: any): number), getEstimatedTotalSize: ({ itemCount, itemSize }: Props) => @@ -23,12 +23,13 @@ const FixedSizeList = createListComponent({ // TODO Deprecate direction "horizontal" const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; const size = (((isHorizontal ? width : height): any): number); - const maxOffset = Math.max( + const lastItemOffset = Math.max( 0, - Math.min( - itemCount * ((itemSize: any): number) - size, - index * ((itemSize: any): number) - ) + itemCount * ((itemSize: any): number) - size + ); + const maxOffset = Math.min( + lastItemOffset, + index * ((itemSize: any): number) ); const minOffset = Math.max( 0, @@ -51,13 +52,25 @@ const FixedSizeList = createListComponent({ return maxOffset; case 'end': return minOffset; - case 'center': - return Math.round(minOffset + (maxOffset - minOffset) / 2); + case 'center': { + // "Centered" offset is usually the average of the min and max. + // But near the edges of the list, this doesn't hold true. + const middleOffset = Math.round( + minOffset + (maxOffset - minOffset) / 2 + ); + if (middleOffset < Math.ceil(size / 2)) { + return 0; // near the beginning + } else if (middleOffset > lastItemOffset + Math.floor(size / 2)) { + return lastItemOffset; // near the end + } else { + return middleOffset; + } + } case 'auto': default: if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { return scrollOffset; - } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + } else if (scrollOffset < minOffset) { return minOffset; } else { return maxOffset; @@ -83,14 +96,14 @@ const FixedSizeList = createListComponent({ const isHorizontal = direction === 'horizontal' || layout === 'horizontal'; const offset = startIndex * ((itemSize: any): number); const size = (((isHorizontal ? width : height): any): number); + const numVisibleItems = Math.ceil( + (size + scrollOffset - offset) / ((itemSize: any): number) + ); return Math.max( 0, Math.min( itemCount - 1, - startIndex + - Math.floor( - (size + (scrollOffset - offset)) / ((itemSize: any): number) - ) + startIndex + numVisibleItems - 1 // -1 is because stop index is inclusive ) ); }, diff --git a/vendor/react-window/src/VariableSizeGrid.js b/vendor/react-window/src/VariableSizeGrid.js index a1273e198e0d..46d59521f197 100644 --- a/vendor/react-window/src/VariableSizeGrid.js +++ b/vendor/react-window/src/VariableSizeGrid.js @@ -274,7 +274,11 @@ const getOffsetForIndexAndAlignment = ( default: if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { return scrollOffset; - } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + } else if (minOffset > maxOffset) { + // Because we only take into account the scrollbar size when calculating minOffset + // this value can be larger than maxOffset when at the end of the list + return minOffset; + } else if (scrollOffset < minOffset) { return minOffset; } else { return maxOffset; diff --git a/vendor/react-window/src/VariableSizeList.js b/vendor/react-window/src/VariableSizeList.js index cebadc663eb2..f4ad10323101 100644 --- a/vendor/react-window/src/VariableSizeList.js +++ b/vendor/react-window/src/VariableSizeList.js @@ -227,7 +227,7 @@ const VariableSizeList = createListComponent({ default: if (scrollOffset >= minOffset && scrollOffset <= maxOffset) { return scrollOffset; - } else if (scrollOffset - minOffset < maxOffset - scrollOffset) { + } else if (scrollOffset < minOffset) { return minOffset; } else { return maxOffset; diff --git a/vendor/react-window/src/createGridComponent.js b/vendor/react-window/src/createGridComponent.js index c72af855c7b2..f2895b60a4ea 100644 --- a/vendor/react-window/src/createGridComponent.js +++ b/vendor/react-window/src/createGridComponent.js @@ -3,7 +3,7 @@ import memoizeOne from 'memoize-one'; import { createElement, PureComponent } from 'react'; import { cancelTimeout, requestTimeout } from './timer'; -import { getScrollbarSize } from './domHelpers'; +import { getScrollbarSize, getRTLOffsetType } from './domHelpers'; import type { TimeoutID } from './timer'; @@ -46,6 +46,22 @@ type OnScrollCallback = ({ type ScrollEvent = SyntheticEvent; type ItemStyleCache = { [key: string]: Object }; +type OuterProps = {| + children: React$Node, + className: string | void, + onScroll: ScrollEvent => void, + style: { + [string]: mixed, + }, +|}; + +type InnerProps = {| + children: React$Node, + style: { + [string]: mixed, + }, +|}; + export type Props = {| children: RenderComponent, className?: string, @@ -56,7 +72,7 @@ export type Props = {| initialScrollLeft?: number, initialScrollTop?: number, innerRef?: any, - innerElementType?: React$ElementType, + innerElementType?: string | React$AbstractComponent, innerTagName?: string, // deprecated itemData: T, itemKey?: (params: {| @@ -67,11 +83,13 @@ export type Props = {| onItemsRendered?: OnItemsRenderedCallback, onScroll?: OnScrollCallback, outerRef?: any, - outerElementType?: React$ElementType, + outerElementType?: string | React$AbstractComponent, outerTagName?: string, // deprecated - overscanColumnsCount?: number, + overscanColumnCount?: number, + overscanColumnsCount?: number, // deprecated overscanCount?: number, // deprecated - overscanRowsCount?: number, + overscanRowCount?: number, + overscanRowsCount?: number, // deprecated rowCount: number, rowHeight: itemSize, style?: Object, @@ -130,10 +148,12 @@ const defaultItemKey = ({ columnIndex, data, rowIndex }) => // In DEV mode, this Set helps us only log a warning once per component instance. // This avoids spamming the console every time a render happens. let devWarningsOverscanCount = null; +let devWarningsOverscanRowsColumnsCount = null; let devWarningsTagName = null; if (process.env.NODE_ENV !== 'production') { if (typeof window !== 'undefined' && typeof window.WeakSet !== 'undefined') { devWarningsOverscanCount = new WeakSet(); + devWarningsOverscanRowsColumnsCount = new WeakSet(); devWarningsTagName = new WeakSet(); } } @@ -320,21 +340,47 @@ export default function createGridComponent({ componentDidMount() { const { initialScrollLeft, initialScrollTop } = this.props; - if (typeof initialScrollLeft === 'number' && this._outerRef != null) { - ((this._outerRef: any): HTMLDivElement).scrollLeft = initialScrollLeft; - } - if (typeof initialScrollTop === 'number' && this._outerRef != null) { - ((this._outerRef: any): HTMLDivElement).scrollTop = initialScrollTop; + + if (this._outerRef != null) { + const outerRef = ((this._outerRef: any): HTMLElement); + if (typeof initialScrollLeft === 'number') { + outerRef.scrollLeft = initialScrollLeft; + } + if (typeof initialScrollTop === 'number') { + outerRef.scrollTop = initialScrollTop; + } } this._callPropsCallbacks(); } componentDidUpdate() { + const { direction } = this.props; const { scrollLeft, scrollTop, scrollUpdateWasRequested } = this.state; - if (scrollUpdateWasRequested && this._outerRef !== null) { - ((this._outerRef: any): HTMLDivElement).scrollLeft = scrollLeft; - ((this._outerRef: any): HTMLDivElement).scrollTop = scrollTop; + + if (scrollUpdateWasRequested && this._outerRef != null) { + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // So we need to determine which browser behavior we're dealing with, and mimic it. + const outerRef = ((this._outerRef: any): HTMLElement); + if (direction === 'rtl') { + switch (getRTLOffsetType()) { + case 'negative': + outerRef.scrollLeft = -scrollLeft; + break; + case 'positive-ascending': + outerRef.scrollLeft = scrollLeft; + break; + default: + const { clientWidth, scrollWidth } = outerRef; + outerRef.scrollLeft = scrollWidth - clientWidth - scrollLeft; + break; + } + } else { + outerRef.scrollLeft = Math.max(0, scrollLeft); + } + + outerRef.scrollTop = Math.max(0, scrollTop); } this._callPropsCallbacks(); @@ -432,7 +478,7 @@ export default function createGridComponent({ ref: innerRef, style: { height: estimatedTotalHeight, - pointerEvents: isScrolling ? 'none' : '', + pointerEvents: isScrolling ? 'none' : undefined, width: estimatedTotalWidth, }, }) @@ -586,6 +632,7 @@ export default function createGridComponent({ _getHorizontalRangeToRender(): [number, number, number, number] { const { columnCount, + overscanColumnCount, overscanColumnsCount, overscanCount, rowCount, @@ -593,7 +640,7 @@ export default function createGridComponent({ const { horizontalScrollDirection, isScrolling, scrollLeft } = this.state; const overscanCountResolved: number = - overscanColumnsCount || overscanCount || 1; + overscanColumnCount || overscanColumnsCount || overscanCount || 1; if (columnCount === 0 || rowCount === 0) { return [0, 0, 0, 0]; @@ -634,13 +681,14 @@ export default function createGridComponent({ const { columnCount, overscanCount, + overscanRowCount, overscanRowsCount, rowCount, } = this.props; const { isScrolling, verticalScrollDirection, scrollTop } = this.state; const overscanCountResolved: number = - overscanRowsCount || overscanCount || 1; + overscanRowCount || overscanRowsCount || overscanCount || 1; if (columnCount === 0 || rowCount === 0) { return [0, 0, 0, 0]; @@ -679,9 +727,11 @@ export default function createGridComponent({ _onScroll = (event: ScrollEvent): void => { const { + clientHeight, clientWidth, scrollLeft, scrollTop, + scrollHeight, scrollWidth, } = event.currentTarget; this.setState(prevState => { @@ -697,24 +747,38 @@ export default function createGridComponent({ const { direction } = this.props; - // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. - // Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). - // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // It's also easier for this component if we convert offsets to the same format as they would be in for ltr. + // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it. let calculatedScrollLeft = scrollLeft; if (direction === 'rtl') { - if (scrollLeft <= 0) { - calculatedScrollLeft = -scrollLeft; - } else { - calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; + switch (getRTLOffsetType()) { + case 'negative': + calculatedScrollLeft = -scrollLeft; + break; + case 'positive-descending': + calculatedScrollLeft = scrollWidth - clientWidth - scrollLeft; + break; } } + // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. + calculatedScrollLeft = Math.max( + 0, + Math.min(calculatedScrollLeft, scrollWidth - clientWidth) + ); + const calculatedScrollTop = Math.max( + 0, + Math.min(scrollTop, scrollHeight - clientHeight) + ); + return { isScrolling: true, horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward', scrollLeft: calculatedScrollLeft, - scrollTop, + scrollTop: calculatedScrollTop, verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward', scrollUpdateWasRequested: false, @@ -768,7 +832,9 @@ const validateSharedProps = ( height, innerTagName, outerTagName, + overscanColumnsCount, overscanCount, + overscanRowsCount, width, }: Props, { instance }: State @@ -779,7 +845,23 @@ const validateSharedProps = ( devWarningsOverscanCount.add(instance); console.warn( 'The overscanCount prop has been deprecated. ' + - 'Please use the overscanColumnsCount and overscanRowsCount props instead.' + 'Please use the overscanColumnCount and overscanRowCount props instead.' + ); + } + } + + if ( + typeof overscanColumnsCount === 'number' || + typeof overscanRowsCount === 'number' + ) { + if ( + devWarningsOverscanRowsColumnsCount && + !devWarningsOverscanRowsColumnsCount.has(instance) + ) { + devWarningsOverscanRowsColumnsCount.add(instance); + console.warn( + 'The overscanColumnsCount and overscanRowsCount props have been deprecated. ' + + 'Please use the overscanColumnCount and overscanRowCount props instead.' ); } } diff --git a/vendor/react-window/src/createListComponent.js b/vendor/react-window/src/createListComponent.js index 86696a0b803e..ae7851e9f402 100644 --- a/vendor/react-window/src/createListComponent.js +++ b/vendor/react-window/src/createListComponent.js @@ -3,6 +3,7 @@ import memoizeOne from 'memoize-one'; import { createElement, PureComponent } from 'react'; import { cancelTimeout, requestTimeout } from './timer'; +import { getRTLOffsetType } from './domHelpers'; import type { TimeoutID } from './timer'; @@ -38,6 +39,22 @@ type onScrollCallback = ({ type ScrollEvent = SyntheticEvent; type ItemStyleCache = { [index: number]: Object }; +type OuterProps = {| + children: React$Node, + className: string | void, + onScroll: ScrollEvent => void, + style: { + [string]: mixed, + }, +|}; + +type InnerProps = {| + children: React$Node, + style: { + [string]: mixed, + }, +|}; + export type Props = {| children: RenderComponent, className?: string, @@ -45,7 +62,7 @@ export type Props = {| height: number | string, initialScrollOffset?: number, innerRef?: any, - innerElementType?: React$ElementType, + innerElementType?: string | React$AbstractComponent, innerTagName?: string, // deprecated itemCount: number, itemData: T, @@ -55,7 +72,7 @@ export type Props = {| onItemsRendered?: onItemsRenderedCallback, onScroll?: onScrollCallback, outerRef?: any, - outerElementType?: React$ElementType, + outerElementType?: string | React$AbstractComponent, outerTagName?: string, // deprecated overscanCount: number, style?: Object, @@ -215,14 +232,13 @@ export default function createListComponent({ componentDidMount() { const { direction, initialScrollOffset, layout } = this.props; - if (typeof initialScrollOffset === 'number' && this._outerRef !== null) { + if (typeof initialScrollOffset === 'number' && this._outerRef != null) { + const outerRef = ((this._outerRef: any): HTMLElement); // TODO Deprecate direction "horizontal" if (direction === 'horizontal' || layout === 'horizontal') { - ((this - ._outerRef: any): HTMLDivElement).scrollLeft = initialScrollOffset; + outerRef.scrollLeft = initialScrollOffset; } else { - ((this - ._outerRef: any): HTMLDivElement).scrollTop = initialScrollOffset; + outerRef.scrollTop = initialScrollOffset; } } @@ -233,12 +249,32 @@ export default function createListComponent({ const { direction, layout } = this.props; const { scrollOffset, scrollUpdateWasRequested } = this.state; - if (scrollUpdateWasRequested && this._outerRef !== null) { + if (scrollUpdateWasRequested && this._outerRef != null) { + const outerRef = ((this._outerRef: any): HTMLElement); + // TODO Deprecate direction "horizontal" if (direction === 'horizontal' || layout === 'horizontal') { - ((this._outerRef: any): HTMLDivElement).scrollLeft = scrollOffset; + if (direction === 'rtl') { + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // So we need to determine which browser behavior we're dealing with, and mimic it. + switch (getRTLOffsetType()) { + case 'negative': + outerRef.scrollLeft = -scrollOffset; + break; + case 'positive-ascending': + outerRef.scrollLeft = scrollOffset; + break; + default: + const { clientWidth, scrollWidth } = outerRef; + outerRef.scrollLeft = scrollWidth - clientWidth - scrollOffset; + break; + } + } else { + outerRef.scrollLeft = scrollOffset; + } } else { - ((this._outerRef: any): HTMLDivElement).scrollTop = scrollOffset; + outerRef.scrollTop = scrollOffset; } } @@ -326,7 +362,7 @@ export default function createListComponent({ ref: innerRef, style: { height: isHorizontal ? '100%' : estimatedTotalSize, - pointerEvents: isScrolling ? 'none' : '', + pointerEvents: isScrolling ? 'none' : undefined, width: isHorizontal ? estimatedTotalSize : '100%', }, }) @@ -496,18 +532,28 @@ export default function createListComponent({ const { direction } = this.props; - // HACK According to the spec, scrollLeft should be negative for RTL aligned elements. - // Chrome does not seem to adhere; its scrolLeft values are positive (measured relative to the left). - // See https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft let scrollOffset = scrollLeft; if (direction === 'rtl') { - if (scrollLeft <= 0) { - scrollOffset = -scrollOffset; - } else { - scrollOffset = scrollWidth - clientWidth - scrollLeft; + // TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. + // This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left). + // It's also easier for this component if we convert offsets to the same format as they would be in for ltr. + // So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it. + switch (getRTLOffsetType()) { + case 'negative': + scrollOffset = -scrollLeft; + break; + case 'positive-descending': + scrollOffset = scrollWidth - clientWidth - scrollLeft; + break; } } + // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. + scrollOffset = Math.max( + 0, + Math.min(scrollOffset, scrollWidth - clientWidth) + ); + return { isScrolling: true, scrollDirection: @@ -519,7 +565,7 @@ export default function createListComponent({ }; _onScrollVertical = (event: ScrollEvent): void => { - const { scrollTop } = event.currentTarget; + const { clientHeight, scrollHeight, scrollTop } = event.currentTarget; this.setState(prevState => { if (prevState.scrollOffset === scrollTop) { // Scroll position may have been updated by cDM/cDU, @@ -528,11 +574,17 @@ export default function createListComponent({ return null; } + // Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds. + const scrollOffset = Math.max( + 0, + Math.min(scrollTop, scrollHeight - clientHeight) + ); + return { isScrolling: true, scrollDirection: - prevState.scrollOffset < scrollTop ? 'forward' : 'backward', - scrollOffset: scrollTop, + prevState.scrollOffset < scrollOffset ? 'forward' : 'backward', + scrollOffset, scrollUpdateWasRequested: false, }; }, this._resetIsScrollingDebounced); diff --git a/vendor/react-window/src/domHelpers.js b/vendor/react-window/src/domHelpers.js index 830aba68d79a..1bdc9dddc10a 100644 --- a/vendor/react-window/src/domHelpers.js +++ b/vendor/react-window/src/domHelpers.js @@ -20,3 +20,53 @@ export function getScrollbarSize(recalculate?: boolean = false): number { return size; } + +export type RTLOffsetType = + | 'negative' + | 'positive-descending' + | 'positive-ascending'; + +let cachedRTLResult: RTLOffsetType | null = null; + +// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements. +// Chrome does not seem to adhere; its scrollLeft values are positive (measured relative to the left). +// Safari's elastic bounce makes detecting this even more complicated wrt potential false positives. +// The safest way to check this is to intentionally set a negative offset, +// and then verify that the subsequent "scroll" event matches the negative offset. +// If it does not match, then we can assume a non-standard RTL scroll implementation. +export function getRTLOffsetType(recalculate?: boolean = false): RTLOffsetType { + if (cachedRTLResult === null || recalculate) { + const outerDiv = document.createElement('div'); + const outerStyle = outerDiv.style; + outerStyle.width = '50px'; + outerStyle.height = '50px'; + outerStyle.overflow = 'scroll'; + outerStyle.direction = 'rtl'; + + const innerDiv = document.createElement('div'); + const innerStyle = innerDiv.style; + innerStyle.width = '100px'; + innerStyle.height = '100px'; + + outerDiv.appendChild(innerDiv); + + ((document.body: any): HTMLBodyElement).appendChild(outerDiv); + + if (outerDiv.scrollLeft > 0) { + cachedRTLResult = 'positive-descending'; + } else { + outerDiv.scrollLeft = 1; + if (outerDiv.scrollLeft === 0) { + cachedRTLResult = 'negative'; + } else { + cachedRTLResult = 'positive-ascending'; + } + } + + ((document.body: any): HTMLBodyElement).removeChild(outerDiv); + + return cachedRTLResult; + } + + return cachedRTLResult; +} diff --git a/yarn.lock b/yarn.lock index 616e6e264520..76c0f0e882c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -766,9 +766,9 @@ regenerator-runtime "^0.12.0" "@babel/runtime@^7.0.0": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12" - integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ== + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" + integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== dependencies: regenerator-runtime "^0.13.2" @@ -5522,10 +5522,10 @@ flatstr@^1.0.9: resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.9.tgz#0950d56fec02de1030c1311847ecd58c25690eb9" integrity sha512-qFlJnOBWDfIaunF54/lBqNKmXOI0HqNhu+mHkLmbaBXlS71PUd9OjFOdyevHt/aHoHB1+eW7eKHgRKOG5aHSpw== -flow-bin@^0.97.0: - version "0.97.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.97.0.tgz#036ffcfc27503367a9d906ec9d843a0aa6f6bb83" - integrity sha512-jXjD05gkatLuC4+e28frH1hZoRwr1iASP6oJr61Q64+kR4kmzaS+AdFBhYgoYS5kpoe4UzwDebWK8ETQFNh00w== +flow-bin@^0.103.0: + version "0.103.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.103.0.tgz#7aec510d85e1c1b0f2b912bb988337d70035cb0f" + integrity sha512-Y3yrnE5ICN1Kl/y10BwjA3JSuS+gt4jVPNyUNCZb0RqmkdssMrW8QNNysJYvhgAY/JBJH8Qv7NVUf11MiwfSlA== fluent-syntax@0.10.0: version "0.10.0" @@ -8250,9 +8250,9 @@ mem@^4.0.0: p-is-promise "^2.0.0" "memoize-one@>=3.1.1 <6": - version "5.0.4" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.4.tgz#005928aced5c43d890a4dfab18ca908b0ec92cbc" - integrity sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA== + version "5.0.5" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.5.tgz#8cd3809555723a07684afafcd6f756072ac75d7e" + integrity sha512-ey6EpYv0tEaIbM/nTDOpHciXUvd+ackQrJgEzBwemhZZIWZjcyodqEcrmqDy2BKRTM3a65kKBV4WtLXJDt26SQ== memoize-one@^3.1.1: version "3.1.1" @@ -10183,9 +10183,9 @@ regenerator-runtime@^0.12.0: integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== regenerator-runtime@^0.13.2: - version "0.13.2" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447" - integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA== + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" + integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== regenerator-runtime@^0.9.5: version "0.9.6"