diff --git a/CHANGELOG.md b/CHANGELOG.md index af800ea0..5b719307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.68.0 +- Add `objectLens` +- Add `arrayLens` + # 1.67.0 - Improvements to `unwind`: use `_.castArray` to avoid unwinding strings and other array-likes - Add `unwindArray` diff --git a/README.md b/README.md index 46bd3c02..3648f5e2 100644 --- a/README.md +++ b/README.md @@ -645,6 +645,10 @@ Returns a function that will set a lens to `false` `value -> arrayLens -> includeLens` An include lens represents membership of a value in a set. It takes a value and lens and returns a new lens - kind of like a "writeable computed" from MobX or Knockout. The view and set functions allow you to read and write a boolean value for whether or not a value is in an array. If you change to true or false, it will set the underlying array lens with a new array either without the value or with it pushed at the end. +#### optionLens +`value -> arrayLens -> optionLens` +Like `includeLens`, `optionLens` takes a value and lens and returns a new lens. The view and set functions allow you to read and write a boolean value for whether the value passed to the `optionLens` is currently selected (meaning whether it matches the value stored in the underlying lens). Setting the `optionLens` to true "selects" its value (sets the value of the underlying lens to it); setting it to false sets the underlying value to `null`. + #### domLens.value `lens -> {value, onChange}` Takes a lens and returns a value/onChange pair that views/sets the lens appropriately. `onChange` sets with `e.target.value` (or `e` if that path isn't present). Example: @@ -676,6 +680,9 @@ Takes a value and returns a function lens for that value. Mostly used for testin ### objectLens Takes a value and returns a object lens for that value. Mostly used for testing and mocking purposes. +### arrayLens +Takes a value and returns an array lens for that value. Mostly used for testing and mocking purposes. + ### stateLens `([value, setValue]) -> lens` Given the popularity of React, we decided to include this little helper that converts a `useState` hook call to a lens. Ex: `let lens = stateLens(useState(false))`. You generally won't use this directly since you can pass the `[value, setter]` pair directly to lens functions diff --git a/package.json b/package.json index ed671963..5d5a70a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "futil-js", - "version": "1.67.0", + "version": "1.68.0", "description": "F(unctional) util(ities). Resistance is futile.", "main": "lib/futil-js.js", "scripts": { diff --git a/src/lens.js b/src/lens.js index 4a5ed506..2cddd922 100644 --- a/src/lens.js +++ b/src/lens.js @@ -14,6 +14,13 @@ export let objectLens = val => ({ val = x }, }) +export let arrayLens = val => { + let result = [val] + result.push(x => { + result[0] = x + }) + return result +} // Lens Conversion export let fnToObj = fn => ({ @@ -52,6 +59,11 @@ export let includeLens = (value, ...lens) => ({ set: x => set(_.uniq(toggleElementBy(!x, value, view(...lens))), ...lens), }) +export let optionLens = (value, ...lens) => ({ + get: () => _.equals(value, view(...lens)), + set: x => set(x ? value : null, ...lens), +}) + // Lens Manipulation //let construct = (...lens) => (lens[1] ? lensProp(...lens) : lens[0]) let lensPair = (get, set) => ({ get, set }) diff --git a/test/lens.spec.js b/test/lens.spec.js index fb12aace..08113261 100644 --- a/test/lens.spec.js +++ b/test/lens.spec.js @@ -19,6 +19,12 @@ describe('Lens Functions', () => { l.set(5) expect(l.get()).to.equal(5) }) + it('arrayLens', () => { + let l = F.arrayLens(1) + expect(l[0]).to.equal(1) + l[1](5) + expect(l[0]).to.equal(5) + }) }) describe('Conversion', () => { it('fnToObj', () => { @@ -78,6 +84,21 @@ describe('Lens Functions', () => { expect(F.view(includesB)).to.be.true expect(object.arr).to.deep.equal(['a', 'c', 'd', 'b']) }) + it('optionLens', () => { + let object = { selected: 'a' } + let optionA = F.optionLens('a', 'selected', object) + let optionB = F.optionLens('b', 'selected', object) + expect(F.view(optionA)).to.be.true + expect(F.view(optionB)).to.be.false + F.on(optionB)() + expect(F.view(optionA)).to.be.false + expect(F.view(optionB)).to.be.true + expect(object.selected).to.equal('b') + F.off(optionB)() + expect(F.view(optionA)).to.be.false + expect(F.view(optionB)).to.be.false + expect(object.selected).to.deep.equal(null) + }) }) describe('Manipulation', () => { it('view', () => { @@ -196,14 +217,7 @@ describe('Lens Functions', () => { }) describe('additional implicit lens formats', () => { it('arrayLens', () => { - let arrayLens = val => { - let result = [val] - result.push(x => { - result[0] = x - }) - return result - } - let lens = arrayLens(false) + let lens = F.arrayLens(false) F.on(lens)() expect(lens[0]).to.be.true F.off(lens)()