Skip to content

Commit

Permalink
chore: refactor modules
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbonnet committed Feb 9, 2019
1 parent 09ceae2 commit bca4d17
Show file tree
Hide file tree
Showing 16 changed files with 701 additions and 703 deletions.
9 changes: 2 additions & 7 deletions src/arrays.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { createElement as $, Component as BaseComponent } from 'react'

import {
setItem,
insertItem,
insertItems,
lazyProperty,
EMPTY_ARRAY,
} from './tools'
import { lazyProperty } from './tools'
import { setItem, insertItem, insertItems, EMPTY_ARRAY } from './immutables'

function onChangeItem(element) {
return (itemValue, itemIndex, payload) => {
Expand Down
60 changes: 60 additions & 0 deletions src/children.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createElement as $ } from 'react'
import { identity, mapValues, map } from 'lodash'
import { withPropsOnChange } from 'recompose'

const DEFAULT_KEYS = ['value', 'name', 'onChange']
const DEFAULT_CHILDREN_PROPS = ({ item }) => (value, index) => item(index)

export function withChildren(
Component,
childProps = DEFAULT_CHILDREN_PROPS,
shouldUpdateOrKeys = DEFAULT_KEYS,
valueName = 'value',
destination = 'children',
) {
/*
Builds an array that maps every item from the `[valueName]` prop with the result of `<Component {...childProps(props)(itemValue, itemIndex)}` and injects it as a `[destination]` prop.
The prop is only updated if `shouldUpdateOrKeys` returns `true` or if a prop whose name is listed in it changes.
Example:
function Item({ value }) {
return $('li', null, value)
}
const List = withChildren(Item, () => value => ({ value }))('ul')
*/
return withPropsOnChange(shouldUpdateOrKeys, (props) => ({
[destination]: map(
props[valueName],
((childProps) => (value, index) =>
$(Component, {
key: index,
...childProps(value, index),
}))(childProps(props)),
),
}))
}

export function withChild(
Component,
childProps = identity,
shouldUpdateOrKeys = DEFAULT_KEYS,
destination = 'children',
) {
/*
Builds an element from the provided `Component` with the props from `childProps(props)` and injects it as a `[destination]` prop.
The prop is only updated if `shouldUpdateOrKeys` returns `true` or if a prop whose name is listed in it changes.
*/
if (typeof Component === 'function') {
return withPropsOnChange(shouldUpdateOrKeys, (props) => ({
[destination]: $(Component, childProps(props, null)),
}))
}
return withPropsOnChange(shouldUpdateOrKeys, (props) => ({
[destination]: mapValues(Component, (Component, name) =>
$(Component, childProps(props, name)),
),
}))
}

export const withElement = withChild
23 changes: 23 additions & 0 deletions src/contexts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createElement as $ } from 'react'

export function withContext(provider, propName) {
/*
Injects a context `provider` that takes its value from `[propName]`.
*/
return (Component) =>
function withContext(props) {
return $(provider, { value: props[propName] }, $(Component, props))
}
}

export function fromContext(consumer, propName) {
/*
Injects the value of the context `consumer` into `[propName]`.
*/
return (Component) =>
function fromContext(props) {
return $(consumer, null, (value) =>
$(Component, { ...props, [propName]: value }),
)
}
}
4 changes: 3 additions & 1 deletion src/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { createElement as $, Component as BaseComponent } from 'react'
import { memoize, get, pickBy } from 'lodash'
import { compose, branch, withHandlers, mapProps } from 'recompose'

import { hasProp, syncedProp, EMPTY_OBJECT } from './tools'
import { syncedProp } from './properties'
import { hasProp } from './tools'
import { EMPTY_OBJECT } from './immutables'

const PROP_NAMES = {
accept: null,
Expand Down
5 changes: 5 additions & 0 deletions src/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class AbortError extends Error {
/*
Error to be thrown in case the query call is aborted.
*/
}
134 changes: 134 additions & 0 deletions src/immutables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { concat, get, indexOf, keys, omit, slice, uniq } from 'lodash'

/*
Empty array to be used in immutable values. Using this instead of `[]` avoids having several instances of immutable empty arrays.
*/
export const EMPTY_ARRAY = []

/*
Empty object to be used in immutable values. Using this instead of `{}` avoids having several instances of immutable empty objects.
*/
export const EMPTY_OBJECT = {}

export function insertItem(
array,
value,
index = array == null ? 0 : array.length,
) {
/*
Returns a new array with the `value` inserted into the `array` at the provided `index`, provided `value` is not `undefined`, in which case the `array` is returned untouched.
If the `index` is not provided, the `value` appended to the `array`.
If the `array` is `nil`, it is considered as an `EMPTY_ARRAY`.
*/
return array == null
? value === undefined
? EMPTY_ARRAY
: [value]
: value === undefined
? array
: [...slice(array, 0, index), value, ...slice(array, index)]
}

export function insertItems(
array,
value,
index = array == null ? 0 : array.length,
) {
/*
Returns a new array with the `value` array merged into the `array` at the provided `index`, provided `value` is not `nil`, in which case the `array` is returned untouched.
If the `index` is not provided, the `value` array is appended to the `array`.
If the `array` is `nil`, it is considered as an `EMPTY_ARRAY`.
*/
return array == null
? value == null
? EMPTY_ARRAY
: value
: value == null
? array
: [...slice(array, 0, index), ...value, ...slice(array, index)]
}

export function replaceItem(array, previousValue, value) {
/*
Returns a new array with the first occurence of the `previousValue` in `array` replaced by `value`.
Returns the same `array` if the `previousValue` is not found.
If the `array` is `nil`, it is considered as an `EMPTY_ARRAY`.
*/
return setItem(array, indexOf(array, previousValue), value)
}

export function setItem(array, index, value) {
/*
Returns a new array with `array[index]` set to `value` if `array[index]` is strictly different from `value`. Otherwise, returns the provided `array`.
If `value` is `undefined`, ensures that the returned array does not contain the item found at `index`.
If `index` is greater than `array.length`, appends `value` to the `array`.
If `index` equals `-1` or is `undefined`, returns the `array` untouched.
If the `array` is `nil`, it is considered as an `EMPTY_ARRAY`.
*/
return index === -1 || index == null
? array == null
? EMPTY_ARRAY
: array
: array == null
? value === undefined
? EMPTY_ARRAY
: [value]
: value === undefined
? index < array.length
? [...slice(array, 0, index), ...slice(array, index + 1)]
: array
: array[index] === value
? array
: [...slice(array, 0, index), value, ...slice(array, index + 1)]
}

export function setProperty(object, key, value) {
/*
Returns a new object with `object[key]` set to `value` if `object[key]` is strictly different from `value`. Otherwise, returns the provided `object`.
If `value` is `undefined`, ensures that the returned object does not contain the `key`.
If `key` is `undefined`, returns the `object` untouched.
If `object` is `nil`, it is considered as an `EMPTY_OBJECT`.
*/
return key === undefined
? object == null
? EMPTY_OBJECT
: object
: object == null
? value === undefined
? EMPTY_OBJECT
: { [key]: value }
: value === undefined
? key in object
? omit(object, key)
: object
: object[key] === value
? object
: { ...object, [key]: value }
}

export function same(
a,
b,
properties = uniq(concat(keys(a), keys(b))),
deep = false,
) {
/*
Returns `true` if objects `a` and `b` have the same `properties`.
Unless provided, `properties` are the combined set of property names from `a` and `b`.
If `deep` is `true`, considers properties as paths (e.g., `p1.p2`).
*/
const { length } = properties
for (let i = 0; i < length; i++) {
const property = properties[i]
if (deep) {
if (get(a, property) !== get(b, property)) {
return false
}
} else {
if (a[property] !== b[property]) {
return false
}
}
}
return true
}
17 changes: 11 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
export * from './arrays'
export * from './objects'
export * from './values'
export * from './booleans'
export * from './strings'
export * from './numbers'
export * from './children'
export * from './contexts'
export * from './dates'

export * from './dom'
export * from './tools'
export * from './errors'
export * from './immutables'
export * from './numbers'
export * from './objects'
export * from './promises'
export * from './properties'
export * from './queries'
export * from './strings'
export * from './tools'
export * from './values'
3 changes: 2 additions & 1 deletion src/numbers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { replace, trim } from 'lodash'
import { branch, withProps } from 'recompose'

import { hasNotProp, escapeRegex, EMPTY_OBJECT } from './tools'
import { hasNotProp, escapeRegex } from './tools'
import { EMPTY_OBJECT } from './immutables'

export const number = branch(
/*
Expand Down
3 changes: 2 additions & 1 deletion src/objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { createElement as $, Component as BaseComponent } from 'react'
import { reduce, join, pick } from 'lodash'
import { compose, branch, withHandlers } from 'recompose'

import { hasProp, setProperty, lazyProperty, EMPTY_OBJECT } from './tools'
import { setProperty, EMPTY_OBJECT } from './immutables'
import { hasProp, lazyProperty } from './tools'

function onChangeProperty(element) {
return (propertyValue, propertyName, payload) => {
Expand Down
16 changes: 16 additions & 0 deletions src/promises.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AbortError } from './errors'

/*
Returns a promise that resolves after at least `duration` milliseconds.
If a `signal` is provided, listens to it to reject
*/
export const waitFor = (duration, signal) =>
new Promise((resolve, reject) => {
const timer = window.setTimeout(resolve, duration)
if (signal) {
signal.addEventListener('abort', () => {
window.clearTimeout(timer)
reject(new AbortError('Aborted'))
})
}
})

0 comments on commit bca4d17

Please sign in to comment.