Vertical two-up (#100)

* Fixing bad property name.

* Allowing two-up to work vertically at smaller widths.

* Switching to orientation attr

* Fixing type and getter/setter behaviour
This commit is contained in:
Jake Archibald
2018-07-20 09:32:18 +01:00
committed by GitHub
parent cc9d01a9ab
commit 6e8f8bbe41
4 changed files with 115 additions and 29 deletions

View File

@@ -1,14 +1,17 @@
import * as styles from './styles.css'; import * as styles from './styles.css';
import { PointerTracker, Pointer } from '../../../../lib/PointerTracker'; import { PointerTracker, Pointer } from '../../../../lib/PointerTracker';
const legacyClipCompat = 'legacy-clip-compat'; const legacyClipCompatAttr = 'legacy-clip-compat';
const orientationAttr = 'orientation';
type TwoUpOrientation = 'horizontal' | 'vertical';
/** /**
* A split view that the user can adjust. The first child becomes * A split view that the user can adjust. The first child becomes
* the left-hand side, and the second child becomes the right-hand side. * the left-hand side, and the second child becomes the right-hand side.
*/ */
export default class TwoUp extends HTMLElement { export default class TwoUp extends HTMLElement {
static get observedAttributes () { return [legacyClipCompat]; } static get observedAttributes() { return [orientationAttr]; }
private readonly _handle = document.createElement('div'); private readonly _handle = document.createElement('div');
/** /**
@@ -52,39 +55,65 @@ export default class TwoUp extends HTMLElement {
}); });
} }
connectedCallback () { connectedCallback() {
this._childrenChange(); this._childrenChange();
if (!this._everConnected) { if (!this._everConnected) {
// Set the initial position of the handle. this._resetPosition();
requestAnimationFrame(() => {
const bounds = this.getBoundingClientRect();
this._position = bounds.width / 2;
this._setPosition();
});
this._everConnected = true; this._everConnected = true;
} }
} }
attributeChangedCallback(name: string) {
if (name === orientationAttr) {
this._resetPosition();
}
}
private _resetPosition() {
// Set the initial position of the handle.
requestAnimationFrame(() => {
const bounds = this.getBoundingClientRect();
this._position = (this.orientation === 'vertical' ? bounds.height : bounds.width) / 2;
this._setPosition();
});
}
/** /**
* If true, this element works in browsers that don't support clip-path (Edge). * If true, this element works in browsers that don't support clip-path (Edge).
* However, this means you'll have to set the height of this element manually. * However, this means you'll have to set the height of this element manually.
*/ */
get noClipPathCompat () { get legacyClipCompat() {
return this.hasAttribute(legacyClipCompat); return this.hasAttribute(legacyClipCompatAttr);
} }
set noClipPathCompat (val: boolean) { set legacyClipCompat(val: boolean) {
if (val) { if (val) {
this.setAttribute(legacyClipCompat, ''); this.setAttribute(legacyClipCompatAttr, '');
} else { } else {
this.removeAttribute(legacyClipCompat); this.removeAttribute(legacyClipCompatAttr);
} }
} }
/**
* Split vertically rather than horizontally.
*/
get orientation(): TwoUpOrientation {
const value = this.getAttribute(orientationAttr);
// This mirrors the behaviour of input.type, where setting just sets the attribute, but getting
// returns the value only if it's valid.
if (value && value.toLowerCase() === 'vertical') return 'vertical';
return 'horizontal';
}
set orientation(val: TwoUpOrientation) {
this.setAttribute(orientationAttr, val);
}
/** /**
* Called when element's child list changes * Called when element's child list changes
*/ */
private _childrenChange () { private _childrenChange() {
// Ensure the handle is the last child. // Ensure the handle is the last child.
// The CSS depends on this. // The CSS depends on this.
if (this.lastElementChild !== this._handle) { if (this.lastElementChild !== this._handle) {
@@ -95,15 +124,20 @@ export default class TwoUp extends HTMLElement {
/** /**
* Called when a pointer moves. * Called when a pointer moves.
*/ */
private _pointerChange (startPoint: Pointer, currentPoint: Pointer) { private _pointerChange(startPoint: Pointer, currentPoint: Pointer) {
const pointAxis = this.orientation === 'vertical' ? 'clientY' : 'clientX';
const dimensionAxis = this.orientation === 'vertical' ? 'height' : 'width';
const bounds = this.getBoundingClientRect(); const bounds = this.getBoundingClientRect();
this._position = this._positionOnPointerStart + (currentPoint.clientX - startPoint.clientX);
this._position = this._positionOnPointerStart +
(currentPoint[pointAxis] - startPoint[pointAxis]);
// Clamp position to element bounds. // Clamp position to element bounds.
this._position = Math.max(0, Math.min(this._position, bounds.width)); this._position = Math.max(0, Math.min(this._position, bounds[dimensionAxis]));
this._setPosition(); this._setPosition();
} }
private _setPosition () { private _setPosition() {
this.style.setProperty('--split-point', `${this._position}px`); this.style.setProperty('--split-point', `${this._position}px`);
} }
} }

View File

@@ -9,8 +9,13 @@ interface Window {
Touch: typeof Touch; Touch: typeof Touch;
} }
interface TwoUpAttributes extends JSX.HTMLAttributes {
'orientation'?: string;
'legacy-clip-compat'?: boolean;
}
declare namespace JSX { declare namespace JSX {
interface IntrinsicElements { interface IntrinsicElements {
'two-up': HTMLAttributes; 'two-up': TwoUpAttributes;
} }
} }

View File

@@ -36,6 +36,17 @@ two-up[legacy-clip-compat] > :not(.twoUpHandle) {
border-radius: 20px; border-radius: 20px;
} }
two-up[orientation='vertical'] .twoUpHandle {
width: auto;
height: 10px;
transform: translateY(var(--split-point)) translateY(-50%);
}
two-up[orientation='vertical'] .twoUpHandle::after {
width: 40px;
height: 80px;
}
two-up > :nth-child(1):not(.twoUpHandle) { two-up > :nth-child(1):not(.twoUpHandle) {
-webkit-clip-path: inset(0 calc(100% - var(--split-point)) 0 0); -webkit-clip-path: inset(0 calc(100% - var(--split-point)) 0 0);
clip-path: inset(0 calc(100% - var(--split-point)) 0 0); clip-path: inset(0 calc(100% - var(--split-point)) 0 0);
@@ -46,6 +57,16 @@ two-up > :nth-child(2):not(.twoUpHandle) {
clip-path: inset(0 0 0 var(--split-point)); clip-path: inset(0 0 0 var(--split-point));
} }
two-up[orientation='vertical'] > :nth-child(1):not(.twoUpHandle) {
-webkit-clip-path: inset(0 0 calc(100% - var(--split-point)) 0);
clip-path: inset(0 0 calc(100% - var(--split-point)) 0);
}
two-up[orientation='vertical'] > :nth-child(2):not(.twoUpHandle) {
-webkit-clip-path: inset(var(--split-point) 0 0 0);
clip-path: inset(var(--split-point) 0 0 0);
}
/* /*
Even in legacy-clip-compat, prefer clip-path if it's supported. Even in legacy-clip-compat, prefer clip-path if it's supported.
It performs way better in Safari. It performs way better in Safari.
@@ -58,4 +79,12 @@ two-up > :nth-child(2):not(.twoUpHandle) {
two-up[legacy-clip-compat] > :nth-child(2):not(.twoUpHandle) { two-up[legacy-clip-compat] > :nth-child(2):not(.twoUpHandle) {
clip: rect(auto auto auto var(--split-point)); clip: rect(auto auto auto var(--split-point));
} }
two-up[orientation='vertical'][legacy-clip-compat] > :nth-child(1):not(.twoUpHandle) {
clip: rect(auto auto var(--split-point) auto);
}
two-up[orientation='vertical'][legacy-clip-compat] > :nth-child(2):not(.twoUpHandle) {
clip: rect(var(--split-point) auto auto auto);
}
} }

View File

@@ -6,21 +6,31 @@ import * as style from './style.scss';
import { bind, drawBitmapToCanvas, linkRef } from '../../lib/util'; import { bind, drawBitmapToCanvas, linkRef } from '../../lib/util';
import { twoUpHandle } from './custom-els/TwoUp/styles.css'; import { twoUpHandle } from './custom-els/TwoUp/styles.css';
type Props = { interface Props {
leftImg: ImageBitmap, leftImg: ImageBitmap;
rightImg: ImageBitmap, rightImg: ImageBitmap;
}; }
type State = {}; interface State {
verticalTwoUp: boolean;
}
export default class Output extends Component<Props, State> { export default class Output extends Component<Props, State> {
state: State = {}; widthQuery = window.matchMedia('(min-width: 500px)');
state: State = {
verticalTwoUp: !this.widthQuery.matches,
};
canvasLeft?: HTMLCanvasElement; canvasLeft?: HTMLCanvasElement;
canvasRight?: HTMLCanvasElement; canvasRight?: HTMLCanvasElement;
pinchZoomLeft?: PinchZoom; pinchZoomLeft?: PinchZoom;
pinchZoomRight?: PinchZoom; pinchZoomRight?: PinchZoom;
retargetedEvents = new WeakSet<Event>(); retargetedEvents = new WeakSet<Event>();
constructor() {
super();
this.widthQuery.addListener(this.onMobileWidthChange);
}
componentDidMount() { componentDidMount() {
if (this.canvasLeft) { if (this.canvasLeft) {
drawBitmapToCanvas(this.canvasLeft, this.props.leftImg); drawBitmapToCanvas(this.canvasLeft, this.props.leftImg);
@@ -39,8 +49,15 @@ export default class Output extends Component<Props, State> {
} }
} }
shouldComponentUpdate(nextProps: Props) { shouldComponentUpdate(nextProps: Props, nextState: State) {
return this.props.leftImg !== nextProps.leftImg || this.props.rightImg !== nextProps.rightImg; return this.props.leftImg !== nextProps.leftImg ||
this.props.rightImg !== nextProps.rightImg ||
this.state.verticalTwoUp !== nextState.verticalTwoUp;
}
@bind
onMobileWidthChange() {
this.setState({ verticalTwoUp: !this.widthQuery.matches });
} }
@bind @bind
@@ -80,10 +97,11 @@ export default class Output extends Component<Props, State> {
this.pinchZoomLeft.dispatchEvent(clonedEvent); this.pinchZoomLeft.dispatchEvent(clonedEvent);
} }
render({ leftImg, rightImg }: Props, { }: State) { render({ leftImg, rightImg }: Props, { verticalTwoUp }: State) {
return ( return (
<div class={style.output}> <div class={style.output}>
<two-up <two-up
orientation={verticalTwoUp ? 'vertical' : 'horizontal'}
// Event redirecting. See onRetargetableEvent. // Event redirecting. See onRetargetableEvent.
onTouchStartCapture={this.onRetargetableEvent} onTouchStartCapture={this.onRetargetableEvent}
onTouchEndCapture={this.onRetargetableEvent} onTouchEndCapture={this.onRetargetableEvent}