Repository URL to install this package:
Version:
0.14.1 ▾
|
/**
* @todo find an optimized understandable way to mixin...
* also split this file probably
*/
import { isArray, isFunction, EMPTY_OBJ, EMPTY_ARRAY } from 'exotic'
import { action, observable, decorate } from 'xmobx/mobx'
import { commonState } from './common'
// === actions ===
export interface SelectableItemAction {
(item: SelectableItem): void
isMobxAction?: boolean
}
export interface SelectableListAction {
(list: SelectableList): void
isMobxAction?: boolean
}
// === list ===
export type SelectableList = Array<SelectableItem>
export interface SelectableListProps {
list: SelectableList
// should be required... but in Select it is not
// and we warn if it's not passed in
onChange?: SelectableItemOnClick
readonly ref?: any
}
export interface DecorateItem {
(item: SelectableItem, index?: number): void
}
export interface SelectableState {
// @todo <SelectableItem>...
list: SelectableItem[]
isVisible: boolean
selectItem: SelectableItemAction
unselectItem: SelectableItemAction
unselectList: SelectableListAction
decorateItem: DecorateItem
handleToggleVisibility(): void
}
// === item ===
export interface SelectableItemOnClick {
(event: Event, item: SelectableItem, state: SelectableState): void
// added if it's an action
isMobxAction?: boolean
// added when we decorate, so we don't decorate 2x
isDecorated?: boolean
}
// @todo CommonState for `.select` etc
export interface SelectableItem {
isSelected?: boolean
isDisabled?: boolean
identifier?: string
label?: string
// this can definitely be anything
value?: any
onClick?: SelectableItemOnClick
}
/**
* @todo belongs in deps...
*/
export const isSatisfiedByIsSelected = item => item.isSelected
/**
* props | props.list | props.options | []
*/
export const toList = (props: SelectableListProps): SelectableList =>
isArray(props)
? props
: isArray(props.list)
? props.list
: isArray(props.options)
? props.options
: EMPTY_ARRAY
/**
* optimizations...
*/
export const isDecorated = (item: SelectableItem): boolean =>
isFunction(item.onClick) && item.onClick.isDecorated === true
export const isEveryItemInListDecoratedWithOnClick = (props: SelectableListProps): boolean =>
toList(props).every(isDecorated)
/**
* This way we can provide the selction state in a list
*/
class SelectionState implements SelectableState {
@observable.ref list: SelectableList = EMPTY_ARRAY
@observable isVisible = false
props: SelectableListProps = EMPTY_OBJ
/**
* @todo @name toggleIsVisible
*/
@action.bound
handleToggleVisibility(): void {
this.isVisible = !this.isVisible
}
@action
setIsVisible(isVisible: boolean): void {
this.isVisible = isVisible
}
/**
* This function selects the particular item
*/
@action
selectItem(item: SelectableItem): void {
item.isSelected = true
}
/**
* This function deSelects the item in the array list
*
* .bound because @see this.unselectList
*/
@action.bound
unselectItem(item: SelectableItem): void {
console.info('[SelectionState] unselecting item: ', item.label)
item.isSelected = false
}
/**
* This function loops the items in the array list
*/
@action
unselectList(list: SelectableList): void {
console.info('[SelectionState] unselecting list: ', list.length)
list.forEach(this.unselectItem)
}
/**
* here we set properties on each item so we can handle each click
*/
@action.bound
decorateItem(item: SelectableItem, index?: number): void {
console.debug('[SelectionState] decorateItem: ' + item.label)
if (isFunction(item.onClick)) {
/**
* we put .isDecorated on this so that we can call it multiple times
* & not double d
*/
if (item.onClick.isDecorated === true) {
console.debug(`[SelectionState] item is already decorated: ${item.label}`)
}
/**
* onChange - (select list level)
* onClick - item level
*/
else if (isFunction(this.props.onChange)) {
console.warn(`[SelectionState] already had onClick ${item.label}`)
console.info(`[SelectionState] - adding a wrapper since we passed in onChange`)
// ref
const itemOnClick = item.onClick
// wrap
const selectionStateItemOnClickWrappingExistingOnClick = (
event: Event
): void => {
console.info('[SelectionState] calling onChange + itemOnClick: ', item.label)
itemOnClick(event, item, this)
this.props.onChange(event, item, this)
}
item.onClick = action(selectionStateItemOnClickWrappingExistingOnClick)
}
else {
console.warn(`[SelectionState] already had onClick ${item.label} and no "onChange" was passed in`)
}
} else {
const selectionStateItemOnClick = (event: Event): void => {
console.info('[SelectionState] selecting item: ', item.label)
this.unselectList(this.list)
this.selectItem(item)
if (isFunction(this.props.onChange)) {
console.info('[SelectionState] calling onChange: ', item.label)
// @todo there is no real event, need to finish standardizing
this.props.onChange(event, item, this)
}
}
// for enforced actions
item.onClick = action(selectionStateItemOnClick)
}
// added either way
item.onClick.isDecorated = true
}
get selectedItem() {
// @todo Default Select Label || this.list[0]
return this.list.find(isSatisfiedByIsSelected)
}
/**
* was named unSelectedListWithSelectedItem
*/
@action
decorateWithProps(props: SelectableListProps): void {
this.props = props
// @note - compat for skreact
this.list = toList(this.props)
// @note - we can see this without logs using mobx devtools
console.debug('[decorateWithProps]')
console.dir(this.list)
this.list.forEach(this.decorateItem)
}
// yagni
// @action setList(list: SelectableList) { this.list = list }
// @action setProps(props: SelectableProps) { this.props = props }
}
function toSelectionState(props: SelectableListProps) {
const state = new SelectionState()
state.decorateWithProps(props)
return state
}
export { SelectionState, toSelectionState }
export default SelectionState