From ffc440ae6aba2bb9863496277246aa68a5e0047b Mon Sep 17 00:00:00 2001 From: Alex Reardon Date: Tue, 12 Jun 2018 10:11:54 +1000 Subject: [PATCH] progress --- package.json | 2 +- src/state/box.js | 32 +++++++ src/types.js | 20 ++++- .../draggable-dimension-publisher.jsx | 88 ++++++++++--------- src/view/draggable/draggable-types.js | 10 +-- src/view/draggable/draggable.jsx | 17 ++-- src/view/placeholder/placeholder.jsx | 44 +--------- yarn.lock | 6 +- 8 files changed, 116 insertions(+), 103 deletions(-) create mode 100644 src/state/box.js diff --git a/package.json b/package.json index 6814adb0f4..882d41afd6 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ }, "dependencies": { "babel-runtime": "^6.26.0", - "css-box-model": "^0.0.13", + "css-box-model": "^0.0.14", "memoize-one": "^3.1.1", "prop-types": "^15.6.1", "raf-schd": "^2.1.2", diff --git a/src/state/box.js b/src/state/box.js new file mode 100644 index 0000000000..ee32c392b0 --- /dev/null +++ b/src/state/box.js @@ -0,0 +1,32 @@ +// @flow +import memoizeOne from 'memoize-one'; +import type { Spacing, Box } from 'css-box-model'; +import type { BoxSpacing, BoxSizing } from '../types'; + +export const withBoxSpacing = memoizeOne((box: Spacing): BoxSpacing => ({ + paddingTop: box.padding.top, + paddingRight: box.padding.right, + paddingBottom: box.padding.bottom, + paddingLeft: box.padding.left, + marginTop: box.margin.top, + marginRight: box.margin.right, + marginBottom: box.margin.bottom, + marginLeft: box.margin.left, + borderTopWidth: box.border.top, + borderRightWidth: box.border.right, + borderBottomWidth: box.border.bottom, + borderLeftWidth: box.border.left, +})); + +type WidthAndHeight = {| + width: number, + height: number, +|} + +export const getBoxSizingHeightAndWidth = memoizeOne((box: Box, boxSizing: BoxSizing): WidthAndHeight => { + const isBorderBox: boolean = boxSizing === 'border-box'; + const width: number = isBorderBox ? box.borderBox.width : box.contentBox.width; + const height: number = isBorderBox ? box.borderBox.height : box.contentBox.height; + + return { width, height }; +}); diff --git a/src/types.js b/src/types.js index e3d034bf1f..d38d3c8c25 100644 --- a/src/types.js +++ b/src/types.js @@ -48,15 +48,33 @@ export type HorizontalAxis = {| export type Axis = VerticalAxis | HorizontalAxis +export type BoxSpacing = {| + marginTop: number, + marginRight: number, + marginBottom: number, + marginLeft: number, + paddingTop: number, + paddingRight: number, + paddingBottom: number, + paddingLeft: number, + borderTopWidth: number, + borderRightWidth: number, + borderBottomWidth: number, + borderLeftWidth: number, +|} + +export type BoxSizing = 'border-box' | 'content-box'; + export type Placeholder = {| client: BoxModel, tagName: string, display: string, - boxSizing: string, + boxSizing: BoxSizing, |} export type DraggableDimension = {| descriptor: DraggableDescriptor, + boxSizing: BoxSizing, // the placeholder for the draggable placeholder: Placeholder, // relative to the viewport when the drag started diff --git a/src/view/draggable-dimension-publisher/draggable-dimension-publisher.jsx b/src/view/draggable-dimension-publisher/draggable-dimension-publisher.jsx index 4bd4b11f7b..c88d430f62 100644 --- a/src/view/draggable-dimension-publisher/draggable-dimension-publisher.jsx +++ b/src/view/draggable-dimension-publisher/draggable-dimension-publisher.jsx @@ -1,10 +1,10 @@ // @flow import { Component, type Node } from 'react'; -import type { Position } from 'css-box-model'; import PropTypes from 'prop-types'; import memoizeOne from 'memoize-one'; import invariant from 'tiny-invariant'; -import { calculateBox, withScroll, type BoxModel, getBox } from 'css-box-model'; +import { calculateBox, withScroll, getBox, offset, type BoxModel, type Position } from 'css-box-model'; +import { negate, subtract } from '../../state/position'; import { dimensionMarshalKey } from '../context-keys'; import type { DraggableDescriptor, @@ -12,6 +12,7 @@ import type { Placeholder, DraggableId, DroppableId, + BoxSizing, } from '../../types'; import type { DimensionMarshal } from '../../state/dimension-marshal/dimension-marshal-types'; @@ -19,31 +20,13 @@ type Props = {| draggableId: DraggableId, droppableId: DroppableId, index: number, + isDragging: boolean, + offset: Position, getDraggableRef: () => ?HTMLElement, getPlaceholderRef: () => ?HTMLElement, children: Node, |} -type PositionStyleToggle = {| - capture: () => void, - clear: () => void, - restore: () => void, -|} - -const style = document.createElement('style'); -style.innerHTML = ` - .alex-test { - /* shifted draggables */ - transform: none !important; - /* dragging item */ - position: inherit !important; - top: inherit !important; - left: inherit !important; - } -`; -const head = document.querySelector('head'); -head.appendChild(style); - export default class DraggableDimensionPublisher extends Component { /* eslint-disable react/sort-comp */ static contextTypes = { @@ -110,49 +93,68 @@ export default class DraggableDimensionPublisher extends Component { getDimension = (windowScroll: Position): DraggableDimension => { const targetRef: ?HTMLElement = this.props.getDraggableRef(); - const placeholderRef: ?HTMLElement = this.props.getPlaceholderRef(); + // const placeholderRef: ?HTMLElement = this.props.getPlaceholderRef(); const descriptor: ?DraggableDescriptor = this.publishedDescriptor; invariant(targetRef, 'DraggableDimensionPublisher cannot calculate a dimension when not attached to the DOM'); invariant(descriptor, 'Cannot get dimension for unpublished draggable'); - const ref: HTMLElement = placeholderRef || targetRef; + // We need to fast forward any transforms so that the collection will be correct + // given the current offset. + // When there is no transition the value will be empty string ("") + const previousTransition: ?string = targetRef.style.transition; + if (previousTransition) { + targetRef.style.transition = 'none'; + } + + // Record values from the DOM + const computedStyles: CSSStyleDeclaration = window.getComputedStyle(targetRef); + const borderBox: ClientRect = targetRef.getBoundingClientRect(); - if (ref === placeholderRef) { - console.log('using placeholder ref for', descriptor.id); + // Reapply inline style transition if there was one + if (previousTransition) { + targetRef.style.transition = previousTransition; } - // TODO: rather than toggling these properties (yuck) - force offset with current offset - const previous = { - transition: ref.style.transition, - transform: ref.style.transform, - }; - ref.style.transition = 'none'; - ref.style.transform = 'none'; + const change: Position = (() => { + const { isDragging, offset: shift } = this.props; + const transform: Position = negate(shift); + console.log('transform', transform); + if (!isDragging) { + return transform; + } + + // When dragging, position: fixed will avoid any client changes based on scroll. + // We are manually undoing that + return subtract(transform, windowScroll); + })(); - const computedStyles: CSSStyleDeclaration = window.getComputedStyle(ref); - const borderBox: ClientRect = ref.getBoundingClientRect(); + // Object.assign(ref.style, previous); - Object.assign(ref.style, previous); + const client: BoxModel = offset(calculateBox(borderBox, computedStyles), change); - const client: BoxModel = calculateBox(borderBox, computedStyles); + console.log(descriptor.id, 'client', client, 'box sizing', computedStyles.boxSizing); const page: BoxModel = withScroll(client, windowScroll); console.warn(descriptor.id, 'collected pageBorderBoxCenter', page.borderBox.center, 'height', client.borderBox.height); - if (placeholderRef) { - const box = getBox(targetRef); - console.warn(descriptor.id, 'targetRef (not placeholder) pageBorderBoxCenter', box.borderBox.center, 'height', box.borderBox.height); - } + // if (placeholderRef) { + // const box = getBox(targetRef); + // console.warn(descriptor.id, 'targetRef (not placeholder) + // pageBorderBoxCenter', box.borderBox.center, 'height', box.borderBox.height); + // } + + const boxSizing: BoxSizing = computedStyles.boxSizing === 'border-box' ? 'border-box' : 'content-box'; const placeholder: Placeholder = { client, - tagName: ref.tagName.toLowerCase(), + tagName: targetRef.tagName.toLowerCase(), display: computedStyles.display, - boxSizing: computedStyles.boxSizing, + boxSizing, }; const dimension: DraggableDimension = { descriptor, + boxSizing, placeholder, client, page, diff --git a/src/view/draggable/draggable-types.js b/src/view/draggable/draggable-types.js index 5e510e1324..31dc8066a2 100644 --- a/src/view/draggable/draggable-types.js +++ b/src/view/draggable/draggable-types.js @@ -7,6 +7,8 @@ import type { DraggableDimension, ZIndex, State, + BoxSpacing, + BoxSizing, } from '../../types'; import { lift, @@ -16,6 +18,7 @@ import { moveDown, moveRight, moveLeft, + drop, dropAnimationFinished, } from '../../state/action-creators'; @@ -39,7 +42,6 @@ export type DraggingStyle = {| // used `box-sizing: content-box` or `box-sizing: border-box` // Because we are setting the width and height directly we want to ensure that // these are the actual values applied - boxSizing: 'border-box', // We initially position the element in the same *visual spot* as when it started. // This means that these values *exclude* the original margins so that element remains @@ -52,10 +54,8 @@ export type DraggingStyle = {| // the element positioned with the top/left position (which is margin aware). // We also clear the margin right / bottom. This has no positioning impact, // but it is cleanest to just remove all the margins rather than only the top and left. - marginTop: number, - marginRight: number, - marginBottom: number, - marginLeft: number, + boxSizing: BoxSizing, + ...BoxSpacing, // We need to opt out of the shared global style that is being applied to // all draggables. The movement of moving draggables is either not animated diff --git a/src/view/draggable/draggable.jsx b/src/view/draggable/draggable.jsx index 5f2d0c64ac..7c5c62df4c 100644 --- a/src/view/draggable/draggable.jsx +++ b/src/view/draggable/draggable.jsx @@ -32,6 +32,7 @@ import type { ZIndexOptions, } from './draggable-types'; import getWindowScroll from '../window/get-window-scroll'; +import { withBoxSpacing, getBoxSizingHeightAndWidth } from '../../state/box'; import type { Speed, Style as MovementStyle } from '../moveable/moveable-types'; export const zIndexOptions: ZIndexOptions = { @@ -210,17 +211,14 @@ export default class Draggable extends Component { // const { width, height, top, left } = dimension.client.borderBox; // For an explanation of properties see `draggable-types`. + const { width, height } = getBoxSizingHeightAndWidth(box, dimension.boxSizing); + const style: DraggingStyle = { // ## Sizing - // Applying the correct border-box sizing - boxSizing: 'border-box', - width: box.borderBox.width, - height: box.borderBox.height, - // Apply margin so that dimension recapturing will get the same marginBox - marginTop: dimension.client.margin.top, - marginRight: dimension.client.margin.right, - marginBottom: dimension.client.margin.bottom, - marginLeft: dimension.client.margin.left, + boxSizing: dimension.boxSizing, + width, + height, + ...withBoxSpacing(box), // ## Placement // As we are applying the margins we need to align to the start of the marginBox top: box.marginBox.top, @@ -381,6 +379,7 @@ export default class Draggable extends Component { draggableId={draggableId} droppableId={droppableId} index={index} + offset={offset} getDraggableRef={this.getDraggableRef} getPlaceholderRef={this.getPlaceholderRef} > diff --git a/src/view/placeholder/placeholder.jsx b/src/view/placeholder/placeholder.jsx index fd6ae8a695..d57e829bd3 100644 --- a/src/view/placeholder/placeholder.jsx +++ b/src/view/placeholder/placeholder.jsx @@ -1,6 +1,6 @@ // @flow import React, { PureComponent } from 'react'; -import type { Spacing } from 'css-box-model'; +import { withBoxSpacing, getBoxSizingHeightAndWidth } from '../../state/box'; import type { Placeholder as PlaceholderType } from '../../types'; type Props = {| @@ -8,41 +8,6 @@ type Props = {| innerRef: (ref: ?HTMLElement) => void, |} -type SpacingMap = {| - top: string, - right: string, - bottom: string, - left: string, -|} - -const fromSpacing = (map: SpacingMap) => (spacing: Spacing) => ({ - [map.top]: spacing.top, - [map.right]: spacing.right, - [map.bottom]: spacing.bottom, - [map.left]: spacing.left, -}); - -const withMargin = fromSpacing({ - top: 'marginTop', - right: 'marginRight', - bottom: 'marginBottom', - left: 'marginLeft', -}); - -const withPadding = fromSpacing({ - top: 'paddingTop', - right: 'paddingRight', - bottom: 'paddingBottom', - left: 'paddingLeft', -}); - -const withBorder = fromSpacing({ - top: 'borderTopWidth', - right: 'borderRightWidth', - bottom: 'borderBottomWidth', - left: 'borderLeftWidth', -}); - export default class Placeholder extends PureComponent { // eslint-disable-next-line react/sort-comp ref: ?HTMLElement = null @@ -73,17 +38,14 @@ export default class Placeholder extends PureComponent { const placeholder: PlaceholderType = this.props.placeholder; const { client, display, tagName, boxSizing } = placeholder; - const width: number = boxSizing === 'borderBox' ? client.borderBox.width : client.contentBox.width; - const height: number = boxSizing === 'borderBox' ? client.borderBox.height : client.contentBox.height; + const { width, height } = getBoxSizingHeightAndWidth(client, boxSizing); const style = { display, boxSizing, width, height, - ...withMargin(client.margin), - ...withPadding(client.padding), - ...withBorder(client.border), + ...withBoxSpacing(client), borderStyle: 'solid', borderColor: 'transparent', diff --git a/yarn.lock b/yarn.lock index ddbd47bc66..fe50644c32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2734,9 +2734,9 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -css-box-model@^0.0.13: - version "0.0.13" - resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-0.0.13.tgz#e9c2a3b25e48ac786f38b7153eeef785c6658000" +css-box-model@^0.0.14: + version "0.0.14" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-0.0.14.tgz#bb10ba311bd07d72c7ed4ce260d3035ecb5b5c57" css-color-keywords@^1.0.0: version "1.0.0"