Skip to content
This repository has been archived by the owner on Aug 21, 2023. It is now read-only.

Commit

Permalink
Merge 85d97ae into 3725422
Browse files Browse the repository at this point in the history
  • Loading branch information
tinkertrain committed Jul 22, 2019
2 parents 3725422 + 85d97ae commit c3290a7
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 112 deletions.
125 changes: 83 additions & 42 deletions src/components/EditableField/EditableField.Input.tsx
Expand Up @@ -112,11 +112,22 @@ export class EditableFieldInput extends React.Component<
}

componentDidMount() {
/* istanbul ignore else */
const { isActive } = this.props

/* istanbul ignore next */
if (!this.props.renderAsBlock) {
this.calculateFieldWidth()
}

this.setInputTitle()

if (isActive) {
/* istanbul ignore next */
if (document.activeElement !== this.optionsDropdownRef) {
const inputNode = this.inputRef
inputNode && inputNode.focus()
}
}
}

shouldComponentUpdate(nextProps, nextState) {
Expand All @@ -137,19 +148,17 @@ export class EditableFieldInput extends React.Component<

componentDidUpdate(prevProps) {
const { isActive } = this.props
const inputNode = this.inputRef
const valueChanged =
this.props.fieldValue.value !== prevProps.fieldValue.value

/* istanbul ignore else */
if (!this.props.renderAsBlock) {
this.calculateFieldWidth(valueChanged)
this.calculateFieldWidth()
}

this.setInputTitle()

if (isActive) {
if (document.activeElement !== this.optionsDropdownRef) {
const inputNode = this.inputRef

inputNode && inputNode.focus()
}
}
Expand All @@ -175,54 +184,86 @@ export class EditableFieldInput extends React.Component<
}
}

calculateFieldWidth = (valueChanged?) => {
const { actions, isActive } = this.props
const { dynamicFieldWidth, staticContentWidth } = this.state
getValueWidth = () => {
const editableFieldInputNode = this.editableFieldInputRef
const parentWidth = editableFieldInputNode.parentElement
? editableFieldInputNode.parentElement.getBoundingClientRect().width
: /* istanbul ignore next */
0
const staticContentNode = this.staticContentRef
const placeholder = staticContentNode.querySelector('.is-placeholder')
const initializedFieldWidth = editableFieldInputNode.getBoundingClientRect()
.width
let fieldWidth =
dynamicFieldWidth == null
? `${initializedFieldWidth}px`
: dynamicFieldWidth
let staticWidth =
staticContentWidth == null || valueChanged
? staticContentNode.getBoundingClientRect().width
: staticContentWidth
let actionsWidth = 0

if (placeholder) {
staticWidth = placeholder.getBoundingClientRect().width
}
const staticOptionNode = staticContentNode.querySelector(
'.EditableField__staticOption'
)
const staticValueNode = staticContentNode.querySelector(
'.EditableField__staticValue'
)
const staticOptionWidth = staticOptionNode
? staticOptionNode.getBoundingClientRect().width + 10
: 0
let contentWidth = 0

// A reliable way to get the width of dynamic content
// is by creating a temporary element
// and get its width
let temporaryValueNode: HTMLDivElement | null = document.createElement(
'div'
)

if (actions) {
actionsWidth = actions.length * 20
}
//@ts-ignore
// Never null
temporaryValueNode.textContent = staticValueNode.textContent

// To get an accurate width, apply the font styles of the field
temporaryValueNode.classList.add('is-temporary-value')
editableFieldInputNode.appendChild(temporaryValueNode)
contentWidth = Math.ceil(
temporaryValueNode.getBoundingClientRect().width + staticOptionWidth
)

// Clean up
editableFieldInputNode.removeChild(temporaryValueNode)
temporaryValueNode = null

// The max width should be the parent width
return contentWidth >= parentWidth
? parentWidth
: /* istanbul ignore next */ contentWidth
}

calculateFieldWidth = () => {
const { actions, isActive } = this.props

if (isActive) {
fieldWidth = '100%'
this.setState({
dynamicFieldWidth: '100%',
staticContentWidth: '100%',
})
} else {
const editableFieldInputNode = this.editableFieldInputRef
const parentWidth = editableFieldInputNode.parentElement
? editableFieldInputNode.parentElement.getBoundingClientRect().width
: /* istanbul ignore next */
0
const placeholderNode = this.staticContentRef.querySelector(
'.is-placeholder'
)
const staticContentWidth = placeholderNode
? placeholderNode.getBoundingClientRect().width
: this.getValueWidth()
const actionsWidth = actions ? actions.length * 25 : 0

// If we add the actions width to the static content width and it's actually larger than the container
// take the actions width off to make space for them
/* istanbul ignore next */
if (staticWidth && staticWidth + actionsWidth > parentWidth) {
/* istanbul ignore next */
fieldWidth = `${staticWidth - actionsWidth}px`
} else {
fieldWidth = `${staticWidth}px`
}
const fieldCSSWidth =
staticContentWidth + actionsWidth > parentWidth
? `${staticContentWidth - actionsWidth}px`
: `${staticContentWidth}px`

this.setState({
dynamicFieldWidth: fieldCSSWidth,
staticContentWidth: `${staticContentWidth}px`,
})
}

this.setState({
dynamicFieldWidth: fieldWidth,
staticContentWidth: staticWidth,
})
}

getClassName() {
Expand Down Expand Up @@ -427,7 +468,7 @@ export class EditableFieldInput extends React.Component<
this.handleActionClick({ action, event })
}}
>
<Icon name={action.icon || ACTION_ICONS[action.name]} size="16" />
<Icon name={action.icon || ACTION_ICONS[action.name]} size="24" />
</FieldButtonUI>
)
})}
Expand Down
84 changes: 54 additions & 30 deletions src/components/EditableField/EditableField.tsx
Expand Up @@ -78,11 +78,7 @@ export class EditableField extends React.Component<
let defaultStateOption: string | null = null

if (valueOptions) {
if (defaultOption) {
defaultStateOption = defaultOption
} else {
defaultStateOption = valueOptions[0]
}
defaultStateOption = defaultOption ? defaultOption : valueOptions[0]
}

const initialFieldValue = normalizeFieldValue({
Expand Down Expand Up @@ -185,17 +181,26 @@ export class EditableField extends React.Component<
handleInputKeyDown = ({ event, name }) => {
return new Promise(resolve => {
const { onEnter, onEscape, onCommit, onDiscard } = this.props
const { initialFieldValue, fieldValue } = this.state
const {
initialFieldValue,
fieldValue,
multipleValuesEnabled,
} = this.state
const inputValue = event.currentTarget.value
const isEnter = event.key === key.ENTER
const isEscape = event.key === key.ESCAPE
const cachedEvent = { ...event }

if (isEnter) {
const impactedField = find(fieldValue, val => val.id === name)
// Case 1: value was not changed
const impactedField = find(initialFieldValue, val => val.id === name)
// Case 1: in multi-value fields if value is empty
// Do nothing
if (multipleValuesEnabled && inputValue === '') {
return
}
// Case 2: value was not changed
// Just change active status
if (inputValue === impactedField.value) {
else if (impactedField && inputValue === impactedField.value) {
this.setState({ activeField: '' }, () => {
resolve()

Expand Down Expand Up @@ -282,8 +287,9 @@ export class EditableField extends React.Component<
const { onAdd } = this.props
const { fieldValue, defaultOption } = this.state
const isNotSingleEmptyValue = fieldValue[fieldValue.length - 1].value !== ''

/* istanbul ignore next */
if (isNotSingleEmptyValue) {
// it is tested
const { name } = this.props
const newValueObject = createNewFieldValue(
{
Expand All @@ -305,7 +311,7 @@ export class EditableField extends React.Component<

handleDeleteAction = ({ action, name, event }) => {
const { onCommit, onDelete } = this.props
const { fieldValue, defaultOption } = this.state
const { defaultOption, fieldValue } = this.state
const cachedEvent = { ...event }
let updatedFieldValue: FieldValue[]

Expand All @@ -327,14 +333,22 @@ export class EditableField extends React.Component<
updatedFieldValue = fieldValue.filter(val => val.id !== name)
}

this.setState({ fieldValue: updatedFieldValue }, () => {
onDelete({ name, value: this.state.fieldValue, event })
onCommit({ name, value: this.state.fieldValue })

if (action.callback && typeof action.callback === 'function') {
action.callback({ name, action, value: fieldValue, event: cachedEvent })
this.setState(
{ fieldValue: updatedFieldValue, initialFieldValue: updatedFieldValue },
() => {
onDelete({ name, value: this.state.fieldValue, event })
onCommit({ name, value: this.state.fieldValue })

if (action.callback && typeof action.callback === 'function') {
action.callback({
name,
action,
value: fieldValue,
event: cachedEvent,
})
}
}
})
)
}

handleCustomAction = ({ action, name, event }) => {
Expand All @@ -350,13 +364,12 @@ export class EditableField extends React.Component<
if (!this.state.activeField) return

const targetNode = event.target

/* istanbul ignore else */
if (targetNode instanceof Element) {
/* istanbul ignore if */
if (document.activeElement === targetNode) return
/* istanbul ignore if */
if (this.editableFieldRef.contains(targetNode)) return
/* istanbul ignore if */
if (targetNode.classList.contains('c-DropdownV2Item')) return

const { name } = this.props
Expand Down Expand Up @@ -414,7 +427,26 @@ export class EditableField extends React.Component<
}
}

renderInputFields() {
renderAddButton = () => {
const { disabled } = this.props
const { fieldValue, multipleValuesEnabled } = this.state

const isLastValueEmpty = fieldValue[fieldValue.length - 1].value === ''
const isSingleAndEmpty = fieldValue.length === 1 && isLastValueEmpty

return multipleValuesEnabled && !isSingleAndEmpty && !disabled ? (
<AddButtonUI
className="EditableField_addButton"
type="button"
onClick={this.handleAddValue}
disabled={isLastValueEmpty}
>
<Icon name={ACTION_ICONS['plus']} size="20" />
</AddButtonUI>
) : null
}

renderInputFields = () => {
const {
name,
disabled,
Expand Down Expand Up @@ -462,15 +494,7 @@ export class EditableField extends React.Component<
)
})}

{multipleValuesEnabled && !disabled ? (
<AddButtonUI
className="EditableField_addButton"
type="button"
onClick={this.handleAddValue}
>
<Icon name={ACTION_ICONS['plus']} size="20" />
</AddButtonUI>
) : null}
{this.renderAddButton()}
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/EditableField/EditableField.types.ts
Expand Up @@ -102,7 +102,7 @@ export interface EditableFieldInputProps {

export interface EditableFieldInputState {
dynamicFieldWidth: string | null
staticContentWidth: number | null
staticContentWidth: string | null
}

export interface TruncateProps {
Expand Down
6 changes: 3 additions & 3 deletions src/components/EditableField/EditableField.utils.ts
Expand Up @@ -5,9 +5,9 @@ import { find } from '../../utilities/arrays'
export const COMPONENT_KEY = 'EditableField'

export const ACTION_ICONS = {
delete: 'cross-medium',
link: 'new-window-large',
plus: 'plus-medium',
delete: 'cross-small',
link: 'new-window',
plus: 'plus-small',
valueOption: 'chevron-down',
}

Expand Down

0 comments on commit c3290a7

Please sign in to comment.