Skip to content

Commit

Permalink
Rework refs handling and focus on enter mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
paradoxxxzero committed May 7, 2018
1 parent 37ea831 commit 33f2cd2
Show file tree
Hide file tree
Showing 19 changed files with 361 additions and 339 deletions.
15 changes: 5 additions & 10 deletions src/Field.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,13 @@ class Field extends React.Component {
transientItem,
fields,
i18n,
refs,
errors,
focused,
readOnly,
focusNextOnEnter,
handleFocus,
handleBlur,
handleKeyDown,
handleChange,
handleSubmit,
} = context

if (!transientItem) {
Expand All @@ -67,10 +65,6 @@ class Field extends React.Component {
const Label = TypeField.formolFieldLabelElement || 'label'

const options = {}
if (focusNextOnEnter) {
options.onKeyDown = e => focusNext(e, name, type, refs, handleSubmit)
}

return (
<div
className={b.mix(className).m({
Expand All @@ -86,18 +80,19 @@ class Field extends React.Component {
name={name}
value={value}
type={type}
ref={ref => (refs[name] = ref)}
readOnly={readOnly}
i18n={i18n}
className={b.e('field').m({ type, focus, modified })}
onFocus={e => handleFocus(name, e)}
onBlur={e => handleBlur(name, e)}
onChange={v => {
onChange={(v, target) => {
v = valueFormatter(v)
customValidator &&
refs[name].setCustomValidity(customValidator(v, transientItem))
target &&
target.setCustomValidity(customValidator(v, transientItem))
return handleChange(name, v)
}}
onKeyDown={handleKeyDown}
{...options}
{...props}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/Field.sass
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
color: $text
cursor: pointer
font: $m $font-family
outline: none
// outline: none
padding: 1em .5em

&:focus, &--focus
box-shadow: 0 0 0 $light
cursor: text
outline: none
// outline: none
&:required + .Field__label-text
&::after
Expand Down
63 changes: 49 additions & 14 deletions src/Formol.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,23 @@ export default class Formol extends React.Component {

constructor(props) {
super(props)
const { item, fields, i18n, readOnly, focusNextOnEnter } = props
this.ref = {}
const { item, fields, i18n, readOnly } = props
this.form = null
this.submit = null
this.state = {
disablePrompt: false,
context: {
item,
transientItem: this.fromItem(item),
fields: { ...Formol.defaultFields, ...fields },
i18n: Formol.i18n[i18n],
focusNextOnEnter,
refs: this.ref,
errors: {},
focused: null,
readOnly,
handleKeyDown: this.handleKeyDown.bind(this),
handleFocus: this.handleFocus.bind(this),
handleBlur: this.handleBlur.bind(this),
handleChange: this.handleChange.bind(this),
handleSubmit: this.handleSubmit.bind(this),
},
}
this.handleCancel = this.handleCancel.bind(this)
Expand All @@ -101,11 +100,6 @@ export default class Formol extends React.Component {
readOnly: nextProps.readOnly,
})
}
if (nextProps.focusNextOnEnter !== this.props.focusNextOnEnter) {
this.setContextState({
focusNextOnEnter: nextProps.focusNextOnEnter,
})
}
if (nextProps.fields !== this.props.fields) {
this.setContextState({
fields: { ...Formol.defaultFields, ...nextProps.fields },
Expand Down Expand Up @@ -149,7 +143,7 @@ export default class Formol extends React.Component {
onSubmit,
} = this.props
const { transientItem } = this.state.context
if (this.ref.form.checkValidity()) {
if (this.form.checkValidity()) {
if (onSubmit) {
try {
const report = await onSubmit(transientItem)
Expand Down Expand Up @@ -207,7 +201,7 @@ export default class Formol extends React.Component {
}
}
} else {
this.ref.submit && this.ref.submit.click()
this.submit && this.submit.click()
e.preventDefault()
}
}
Expand Down Expand Up @@ -243,6 +237,47 @@ export default class Formol extends React.Component {
})
}

handleKeyDown(e) {
const { focusNextOnEnter } = this.props
if (focusNextOnEnter && e.keyCode === 13) {
// GORE HACK but it makes everything simpler
const focused = e.target
if (!e.ctrlKey) {
if (focused.tagName === 'TEXTAREA') {
return
}
if (focused.getAttribute('contenteditable')) {
return
}
}
const fields = [...this.form.querySelectorAll('.Formol_Field')]
const focusables = fields.map(field => [
...field.querySelectorAll(`
input:not([disabled]):not([tabindex='-1']),
select:not([disabled]):not([tabindex='-1']),
textarea:not([disabled]):not([tabindex='-1']),
[tabindex]:not([tabindex='-1']),
[contentEditable=true]:not([tabindex='-1'])
`),
])
const focusedFieldIndex = focusables.findIndex(focusableFields =>
focusableFields.includes(focused)
)
const step = e.shiftKey ? -1 : +1
const nextFieldIndex = (focusedFieldIndex + step) % fields.length
if (step === 1 && focusedFieldIndex === fields.length - 1) {
this.handleSubmit(e)
e.preventDefault()
return false
}
// Let's focus the first focusable
const [nextFieldFirstFocusable] = focusables[nextFieldIndex]
nextFieldFirstFocusable.focus()
e.preventDefault()
return false
}
}

handleError(err) {
const {
noNotifications,
Expand Down Expand Up @@ -315,7 +350,7 @@ export default class Formol extends React.Component {
errors: !!Object.keys(context.errors).length,
})}
onSubmit={e => e.preventDefault()}
ref={ref => (this.ref.form = ref)}
ref={ref => (this.form = ref)}
>
{Prompt && (
<Prompt
Expand All @@ -334,7 +369,7 @@ export default class Formol extends React.Component {
<Fragment>
<input
type="submit"
ref={ref => (this.ref.submit = ref)}
ref={ref => (this.submit = ref)}
style={{ display: 'none' }}
/>
<Btn
Expand Down
4 changes: 3 additions & 1 deletion src/async/CalendarField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export default class CalendarField extends React.Component {
onChange(
isDate(newDate)
? format(newDate, 'YYYY-MM-DD', { locale })
: newDate || null
: newDate || null,
this.daypicker.input
)
}

Expand Down Expand Up @@ -61,6 +62,7 @@ export default class CalendarField extends React.Component {
overlay: b.e('overlay').mix('DayPickerInput-Overlay').s,
}}
value={date}
ref={ref => (this.daypicker = ref)}
placeholder={placeholder || dateFormat}
format={dateFormat}
formatDate={(value_, format_) => format(value_, format_, { locale })}
Expand Down
4 changes: 3 additions & 1 deletion src/async/FileField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ export default class FileField extends React.Component {
}
onChange(
// Removing error files and adding new files and new errors
[...value.filter(f => f.data), ...newFiles, ...erroredFiles]
[...value.filter(f => f.data), ...newFiles, ...erroredFiles],
this.dropzone.fileInputEl
)
}

Expand Down Expand Up @@ -173,6 +174,7 @@ export default class FileField extends React.Component {
rejectClassName={b.e('dropzone').m({ reject: true }).s}
disabledClassName={b.e('dropzone').m({ disabled: true }).s}
name={name}
ref={ref => (this.dropzone = ref)}
multiple={multiple}
onDrop={this.handleDrop}
inputProps={inputProps}
Expand Down
12 changes: 8 additions & 4 deletions src/async/HTMLField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default class HTMLField extends React.Component {
this.state = {
editorState: null,
}
this.onChange = this.onChange.bind(this)
}

componentWillMount() {
Expand Down Expand Up @@ -69,6 +70,7 @@ export default class HTMLField extends React.Component {
const value = this.prepareValue(stateToValue(editorState))
this.value = value
this.setState({ editorState })
debugger
onChange(value)
}

Expand Down Expand Up @@ -106,10 +108,12 @@ export default class HTMLField extends React.Component {
localization={{
locale: 'fr',
}}
onBlur={e => onBlur(e)}
onEditorStateChange={state => this.onChange(state)}
onFocus={e => onFocus(e)}
onKeyDown={e => onKeyDown(e)}
onBlur={onBlur}
ref={ref => (this.drafteditor = ref)}
/* eslint-disable-next-line react/jsx-handler-names */
onEditorStateChange={this.onChange}
onFocus={onFocus}
handleReturn={onKeyDown}
placeholder={placeholder || i18n.html.placeholder}
readOnly={readOnly}
required={required}
Expand Down
21 changes: 17 additions & 4 deletions src/async/PasswordStrengthField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,28 @@ import { block } from '../utils'

@block
export default class PasswordStrengthField extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}

handleChange({ password, isValid }) {
const { onChange } = this.props
onChange(
isValid ? password : void 0,
this.passwordInput.reactPasswordStrengthInput
)
}

render(b) {
const { className, name, value, i18n, onChange, ...props } = this.props
const { className, name, value, i18n, ...props } = this.props
return (
<ReactPasswordStrength
changeCallback={({ password, isValid }) =>
onChange(isValid ? password : void 0)
}
/* eslint-disable-next-line react/jsx-handler-names */
changeCallback={this.handleChange}
defaultValue={value}
className={b.mix(className).s}
ref={ref => (this.passwordInput = ref)}
inputProps={{ name, ...props, type: 'password' }}
scoreWords={[
i18n.passwordStrength.weak,
Expand Down
4 changes: 3 additions & 1 deletion src/async/SelectMenuField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default class SelectMenuField extends React.Component {
newValue &&
(multiple
? newValue.map(({ value }) => maybeParse(value))
: maybeParse(newValue.value))
: maybeParse(newValue.value)),
this.select.input.input
)
}

Expand Down Expand Up @@ -43,6 +44,7 @@ export default class SelectMenuField extends React.Component {
return (
<Select
className={b.mix(className).s}
ref={ref => (this.select = ref)}
disabled={readOnly /* There's no readOnly */}
options={options}
multi={multiple}
Expand Down
2 changes: 1 addition & 1 deletion src/fields/BooleanField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default class BooleanField extends React.Component {
return (
<input
checked={value}
onChange={e => onChange(e.target.checked)}
onChange={e => onChange(e.target.checked, e.target)}
{...props}
/>
)
Expand Down
5 changes: 3 additions & 2 deletions src/fields/CheckboxesField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ export default class CheckboxesField extends React.Component {
<FieldSet
className={b}
isChecked={(choice, value) => value.includes(choice)}
onChange={(choice, value, checked) =>
onChange={(choice, value, checked, target) =>
onChange(
checked ? [...value, choice] : value.filter(val => val !== choice)
checked ? [...value, choice] : value.filter(val => val !== choice),
target
)
}
type="checkbox"
Expand Down
10 changes: 1 addition & 9 deletions src/fields/InputField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,11 @@ import React from 'react'
export default class InputField extends React.Component {
static defaultProps = { type: 'text' }

focus() {
this.native && this.native.focus()
}

render() {
// eslint-disable-next-line no-unused-vars
const { i18n, onChange, ...props } = this.props
return (
<input
{...props}
ref={ref => (this.native = ref)}
onChange={e => onChange(e.target.value)}
/>
<input {...props} onChange={e => onChange(e.target.value, e.target)} />
)
}
}
7 changes: 1 addition & 6 deletions src/fields/NumberField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@ import InputField from './InputField'

// eslint-disable-next-line react/prefer-stateless-function
export default class NumberField extends React.Component {
focus() {
this.native && this.native.focus()
}

render() {
// eslint-disable-next-line no-unused-vars
const { i18n, onChange, ...props } = this.props
return (
<InputField
{...props}
ref={ref => (this.native = ref)}
onChange={v => onChange(parseInt(v))}
onChange={(v, target) => onChange(parseInt(v), target)}
/>
)
}
Expand Down
4 changes: 3 additions & 1 deletion src/fields/RadiosField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export default class RadiosField extends React.Component {
<FieldSet
className={b}
isChecked={(choice, value) => choice === value}
onChange={(choice, value, checked) => checked && onChange(choice)}
onChange={(choice, value, checked, target) =>
checked && onChange(choice, target)
}
{...props}
type="radio"
/>
Expand Down
5 changes: 4 additions & 1 deletion src/fields/SelectField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export default class SelectField extends React.Component {
props.disabled = true
}
return (
<select onChange={e => onChange(e.target.value)} {...cleanProps(props)}>
<select
onChange={e => onChange(e.target.value, e.target)}
{...cleanProps(props)}
>
{choices.every(([k]) => k) && <option value="" />}
{choices.map(([label, key]) => (
<option key={key} value={key}>
Expand Down

0 comments on commit 33f2cd2

Please sign in to comment.