Skip to content

Commit 4e58e10

Browse files
committed
[components] Click to scroll for block editor #76
1 parent 7308de4 commit 4e58e10

File tree

4 files changed

+94
-88
lines changed

4 files changed

+94
-88
lines changed

packages/@sanity/components/src/clickToScroll/ClickToScroll.js

Whitespace-only changes.

packages/@sanity/components/src/utilities/ActivateOnFocus.js

+14-50
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,23 @@ class ActivateOnFocus extends React.Component {
77
static propTypes = {
88
children: PropTypes.node.isRequired,
99
message: PropTypes.string,
10-
isActive: PropTypes.bool,
11-
enableBlur: PropTypes.bool,
12-
onFocus: PropTypes.func,
13-
onBlur: PropTypes.func,
10+
isActive: PropTypes.bool
1411
}
1512

1613
static defaultProps = {
17-
enableBlur: true,
1814
message: 'Click to activate…',
19-
isActive: false,
20-
onFocus() {},
21-
onBlur() {},
15+
isActive: false
2216
}
2317

2418
state = {
2519
hasFocus: false
2620
}
2721

28-
setEventHandlerElement = element => {
29-
this._eventHandlerElement = element
30-
}
31-
3222
handleClick = event => {
3323
if (!this.state.hasFocus) {
3424
this.setState({
3525
hasFocus: true
3626
})
37-
this.props.onFocus()
3827
}
3928
}
4029

@@ -43,51 +32,26 @@ class ActivateOnFocus extends React.Component {
4332
this.setState({
4433
hasFocus: false
4534
})
46-
this.props.onBlur()
47-
}
48-
}
49-
50-
handleFocus = event => {
51-
this.setState({
52-
hasFocus: true
53-
})
54-
this.props.onFocus()
55-
}
56-
57-
handleBlur = event => {
58-
const {enableBlur} = this.props
59-
if (enableBlur) {
60-
this.setState({
61-
hasFocus: false
62-
})
6335
}
64-
this.props.onBlur()
6536
}
6637

6738
render() {
6839
const {message, children, isActive} = this.props
6940
const {hasFocus} = this.state
7041

71-
if (isActive) {
72-
return children
73-
}
74-
7542
return (
76-
<div
77-
className={hasFocus ? styles.hasFocus : styles.noFocus}
78-
onFocus={this.handleFocus}
79-
onBlur={this.handleBlur}
80-
>
81-
<div
82-
className={styles.eventHandler}
83-
onClick={this.handleClick}
84-
>
85-
<div className={styles.overlay} />
86-
<div className={styles.message}>{message}</div>
87-
</div>
88-
<div className={styles.content}>
89-
{children}
90-
</div>
43+
<div className={hasFocus ? styles.hasFocus : styles.noFocus}>
44+
{!isActive && (
45+
<div
46+
className={styles.eventHandler}
47+
onClick={this.handleClick}
48+
ref={this.setEventHandlerElement}
49+
>
50+
<div className={styles.overlay} />
51+
<div className={styles.message}>{message}</div>
52+
</div>
53+
)}
54+
<div className={styles.content}>{children}</div>
9155
</div>
9256
)
9357
}

packages/@sanity/components/src/utilities/styles/ActivateOnFocus.css

+8-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
position: relative;
55
width: 100%;
66
height: 100%;
7-
lost-utility: clearfix;
87
box-sizing: border-box;
98
outline: none !important;
109
}
@@ -17,29 +16,28 @@
1716
composes: root;
1817
}
1918

20-
.content {
21-
position: relative;
22-
z-index: 0;
23-
lost-utility: clearfix;
24-
}
25-
2619
.eventHandler {
2720
position: absolute;
2821
top: 0;
2922
left: 0;
3023
width: 100%;
3124
height: 100%;
32-
z-index: 2;
25+
z-index: 1;
26+
box-sizing: border-box;
3327

3428
@nest .hasFocus & {
3529
pointer-events: none;
3630
}
3731
}
3832

33+
.content {
34+
position: relative;
35+
z-index: 0;
36+
}
37+
3938
.overlay {
40-
composes: frosted from "part:@sanity/base/theme/layout/backgrounds-style";
4139
background-color: color(var(--component-bg) a(75%));
42-
z-index: 1;
40+
z-index: 2;
4341
width: 100%;
4442
height: 100%;
4543
position: absolute;
@@ -54,7 +52,6 @@
5452

5553
@nest .hasFocus & {
5654
opacity: 0;
57-
pointer-events: none;
5855
}
5956
}
6057

packages/@sanity/form-builder/src/inputs/BlockEditor-slate/BlockEditor.js

+72-27
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {Data, State} from 'slate'
55
import {Editor} from 'slate-react'
66
import FullscreenDialog from 'part:@sanity/components/dialogs/fullscreen?'
77
import ScrollContainer from 'part:@sanity/components/utilities/scroll-container'
8+
import ActivateOnFocus from 'part:@sanity/components/utilities/activate-on-focus'
89
import {uniqueId} from 'lodash'
910

1011
import FormField from 'part:@sanity/components/formfields/default'
@@ -36,7 +37,9 @@ export default class BlockEditor extends React.Component {
3637

3738
state = {
3839
fullscreen: false,
39-
toolbarStyle: {}
40+
toolbarStyle: {},
41+
preventScroll: false,
42+
editorHasFocus: false
4043
}
4144

4245
_inputId = uniqueId('SlateBlockEditor')
@@ -58,13 +61,30 @@ export default class BlockEditor extends React.Component {
5861
componentDidMount() {
5962
window.addEventListener('keydown', this.handleKeyDown)
6063
// this._inputContainer.addEventListener('mousewheel', this.handleInputScroll)
64+
this.checkScrollHeight()
6165
}
6266

6367
componentWillUnmount() {
6468
window.removeEventListener('keydown', this.handleKeyDown)
6569
// this._inputContainer.removeEventListener('mousewheel', this.handleInputScroll)
6670
}
6771

72+
checkScrollHeight = () => {
73+
if (!this._inputContainer || !this._editorWrapper) {
74+
return
75+
}
76+
77+
const inputHeight = this._inputContainer.offsetHeight
78+
const contentHeight = this._editorWrapper.offsetHeight
79+
80+
if (contentHeight > inputHeight + 100) {
81+
this.setState({
82+
preventScroll: true
83+
})
84+
}
85+
86+
}
87+
6888
handleNodePatch = event => this.props.onNodePatch(event)
6989

7090
handleInsertBlock = item => {
@@ -260,6 +280,19 @@ export default class BlockEditor extends React.Component {
260280
this.editor.focus()
261281
}
262282

283+
handleEditorFocus = event => {
284+
this.setState({
285+
editorHasFocus: true
286+
})
287+
this.focus()
288+
}
289+
290+
handleEditorBlur = event => {
291+
this.setState({
292+
editorHasFocus: false
293+
})
294+
}
295+
263296
handleInputScroll = event => {
264297
// Prevents the parent container to scroll when user tries
265298
// to scroll to the top/bottom of the block editor with momentum scroll or
@@ -294,9 +327,13 @@ export default class BlockEditor extends React.Component {
294327
this._inputContainer = element
295328
}
296329

330+
setEditorWrapper = element => {
331+
this._editorWrapper = element
332+
}
333+
297334
renderBlockEditor() {
298335
const {value, onChange} = this.props
299-
const {fullscreen, toolbarStyle} = this.state
336+
const {fullscreen, toolbarStyle, preventScroll, editorHasFocus} = this.state
300337

301338
return (
302339
<div
@@ -318,31 +355,38 @@ export default class BlockEditor extends React.Component {
318355
decorators={this.getActiveDecorators()}
319356
style={toolbarStyle}
320357
/>
321-
<div
322-
className={styles.inputContainer}
323-
id={this._inputId}
324-
onClick={this.handleEditorContainerClick}
325-
ref={this.setInputContainerElement}
326-
onWheel={this.handleInputScroll}
358+
<ActivateOnFocus
359+
isActive={editorHasFocus || fullscreen || !preventScroll}
360+
message="Click to edit"
327361
>
328-
<div>
329-
<Editor
330-
ref={this.refEditor}
331-
className={styles.input}
332-
onChange={onChange}
333-
placeholder=""
334-
state={value}
335-
blockEditor={this}
336-
plugins={this.slatePlugins}
337-
schema={this.slateSchema}
338-
/>
339-
<div
340-
ref={this.refBlockDragMarker}
341-
style={{display: 'none'}}
342-
className={styles.blockDragMarker}
343-
/>
362+
<div
363+
className={styles.inputContainer}
364+
id={this._inputId}
365+
onClick={this.handleEditorContainerClick}
366+
ref={this.setInputContainerElement}
367+
onWheel={this.handleInputScroll}
368+
>
369+
<div ref={this.setEditorWrapper}>
370+
<Editor
371+
ref={this.refEditor}
372+
className={styles.input}
373+
onChange={onChange}
374+
placeholder=""
375+
state={value}
376+
blockEditor={this}
377+
plugins={this.slatePlugins}
378+
schema={this.slateSchema}
379+
onFocus={this.handleEditorFocus}
380+
onBlur={this.handleEditorBlur}
381+
/>
382+
<div
383+
ref={this.refBlockDragMarker}
384+
style={{display: 'none'}}
385+
className={styles.blockDragMarker}
386+
/>
387+
</div>
344388
</div>
345-
</div>
389+
</ActivateOnFocus>
346390
</div>
347391
)
348392
}
@@ -372,22 +416,23 @@ export default class BlockEditor extends React.Component {
372416
const {type, level} = this.props
373417
const {fullscreen} = this.state
374418
const blockEditor = this.renderBlockEditor()
419+
375420
return (
376421
<FormField
377422
label={type.title}
378423
description={type.description}
379424
labelFor={this._inputId}
380425
level={level}
381426
>
382-
<button tabIndex={0} className={styles.focusSkipper} onClick={() => this.focus()}>Jump to editor</button>
427+
<button tabIndex={0} className={styles.focusSkipper} onClick={() => this.handleEditorFocus()}>Jump to editor</button>
383428
{
384429
fullscreen ? (
385430
<FullscreenDialog isOpen onClose={this.handleFullScreenClose}>
386431
<ScrollContainer className={styles.portal} onScroll={this.handleFullScreenScroll}>
387432
{blockEditor}
388433
</ScrollContainer>
389434
</FullscreenDialog>
390-
) : blockEditor
435+
) : blockEditor
391436
}
392437
</FormField>
393438
)

0 commit comments

Comments
 (0)