Skip to content

Commit

Permalink
✨ Add support for camel case attrs and init keys
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanross committed Jul 7, 2020
1 parent 09eaf96 commit 59d21f1
Show file tree
Hide file tree
Showing 10 changed files with 2,338 additions and 7,886 deletions.
97 changes: 87 additions & 10 deletions build/AnimatedDataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,90 @@ import React from 'react';
import { select } from 'd3-selection';
import 'd3-transition';

function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}

function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}

function _iterableToArrayLimit(arr, i) {
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;

try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);

if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}

return _arr;
}

function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}

function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;

for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];

return arr2;
}

function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}

function mapKeys(obj, fn) {
var entries = Object.entries(obj);
var mapped = entries.map(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
k = _ref2[0],
v = _ref2[1];

return [fn(k), v];
});
return Object.fromEntries(mapped);
}

function parseAttributeName(str) {
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/([A-Z])([A-Z])(?=[a-z])/g, '$1-$2').toLowerCase();
}
function parseEventName(str) {
return str.replace(/on(.*)/, '$1').toLowerCase();
}

function AnimatedDataset(_ref) {
var dataset = _ref.dataset,
attrs = _ref.attrs,
unparsedAttrs = _ref.attrs,
_ref$tag = _ref.tag,
tag = _ref$tag === void 0 ? 'rect' : _ref$tag,
_ref$init = _ref.init,
init = _ref$init === void 0 ? {} : _ref$init,
unparsedInit = _ref$init === void 0 ? {} : _ref$init,
_ref$events = _ref.events,
unparsedEvents = _ref$events === void 0 ? {} : _ref$events,
_ref$keyFn = _ref.keyFn,
keyFn = _ref$keyFn === void 0 ? function (d) {
return d.key;
Expand All @@ -23,12 +100,13 @@ function AnimatedDataset(_ref) {
var refOldAttrs = React.useRef();
React.useLayoutEffect(function () {
if (!ref.current) return;
var attrs = mapKeys(unparsedAttrs, parseAttributeName);
var init = mapKeys(unparsedInit, parseAttributeName);
var events = mapKeys(unparsedEvents, parseEventName);
var attrsList = Object.keys(attrs).filter(function (a) {
return !a.startsWith('on-') && a !== 'text';
});
var attrsListListeners = Object.keys(attrs).filter(function (a) {
return a.startsWith('on-');
return a !== 'text';
});
var eventsList = Object.keys(events);
var oldAttrs = refOldAttrs.current || {};

var animate = function animate() {
Expand All @@ -38,9 +116,8 @@ function AnimatedDataset(_ref) {
sel.attr(a, init.hasOwnProperty(a) ? init[a] : oldAttrs.hasOwnProperty(a) ? oldAttrs[a] : attrs[a]);
});
}).call(function (sel) {
attrsListListeners.forEach(function (a) {
var eventName = a.match(/on-(.*)/)[1];
sel.on(eventName, attrs[a]);
eventsList.forEach(function (event) {
sel.on(event, events[event]);
});
}).call(function (sel) {
var tran = disableAnimation ? sel : sel.transition().delay(delay).duration(duration);
Expand Down Expand Up @@ -71,7 +148,7 @@ function AnimatedDataset(_ref) {
} else {
requestAnimationFrame(animate);
}
}, [dataset, init, keyFn, ref, tag, attrs, duration, disableAnimation]);
}, [dataset, unparsedInit, keyFn, ref, tag, unparsedAttrs, duration, disableAnimation, unparsedEvents]);
return /*#__PURE__*/React.createElement('g', {
ref: ref
});
Expand Down
13 changes: 13 additions & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import * as accurappConfig from 'jest-config-accurapp'
import * as ReactDOMTestUtils from 'react-dom/test-utils'

configure({ adapter: new Adapter() })

global.dispatch = (node, event) =>
ReactDOMTestUtils.act(() => {
node.dispatchEvent(event)
})

global.sleep = ms => new Promise(res => setTimeout(res, ms))

export default {
...accurappConfig,
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"jest": "^25.1.0",
"jest-config-accurapp": "^4.1.1",
"np": "^5.2.1",
"prettier": "^1.19.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"rollup": "^1.30.1",
Expand Down
32 changes: 24 additions & 8 deletions src/AnimatedDataset.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React from 'react'
import { select } from 'd3-selection'
import 'd3-transition'
import { mapKeys } from './mapKey'
import { parseAttributeName, parseEventName } from './parse'

export function AnimatedDataset({
dataset,
attrs,
attrs: unparsedAttrs,
tag = 'rect',
init = {},
init: unparsedInit = {},
events: unparsedEvents = {},
keyFn = d => d.key,
duration = 1000,
delay = 0,
Expand All @@ -18,8 +21,12 @@ export function AnimatedDataset({
React.useLayoutEffect(() => {
if (!ref.current) return

const attrsList = Object.keys(attrs).filter(a => !a.startsWith('on-') && a !== 'text')
const attrsListListeners = Object.keys(attrs).filter(a => a.startsWith('on-'))
const attrs = mapKeys(unparsedAttrs, parseAttributeName)
const init = mapKeys(unparsedInit, parseAttributeName)
const events = mapKeys(unparsedEvents, parseEventName)

const attrsList = Object.keys(attrs).filter(a => a !== 'text')
const eventsList = Object.keys(events)
const oldAttrs = refOldAttrs.current || {}

const animate = () => {
Expand All @@ -44,9 +51,8 @@ export function AnimatedDataset({
})
})
.call(sel => {
attrsListListeners.forEach(a => {
const eventName = a.match(/on-(.*)/)[1]
sel.on(eventName, attrs[a])
eventsList.forEach(event => {
sel.on(event, events[event])
})
})
.call(sel => {
Expand Down Expand Up @@ -96,7 +102,17 @@ export function AnimatedDataset({
} else {
requestAnimationFrame(animate)
}
}, [dataset, init, keyFn, ref, tag, attrs, duration, disableAnimation])
}, [
dataset,
unparsedInit,
keyFn,
ref,
tag,
unparsedAttrs,
duration,
disableAnimation,
unparsedEvents,
])

return React.createElement('g', { ref })
}
5 changes: 5 additions & 0 deletions src/mapKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function mapKeys(obj, fn) {
const entries = Object.entries(obj)
const mapped = entries.map(([k, v]) => [fn(k), v])
return Object.fromEntries(mapped)
}
10 changes: 10 additions & 0 deletions src/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function parseAttributeName(str) {
return str
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.replace(/([A-Z])([A-Z])(?=[a-z])/g, '$1-$2')
.toLowerCase()
}

export function parseEventName(str) {
return str.replace(/on(.*)/, '$1').toLowerCase()
}
45 changes: 45 additions & 0 deletions tests/AnimatedDataset.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,49 @@ describe(AnimatedDataset, () => {

expect(wrapper.find('g').html()).toMatchInlineSnapshot(`"<g><text>Hello World</text></g>"`)
})

it('should accept camel case attribute name', () => {
const wrapper = mount(
<svg>
<AnimatedDataset dataset={dataset} attrs={{ strokeWidth: 10 }} key={p => p.x} />
</svg>
)

expect(wrapper.find('g').html()).toMatchInlineSnapshot(
`"<g><rect stroke-width=\\"10\\"></rect><rect stroke-width=\\"10\\"></rect></g>"`
)
})

it('should accept events', () => {
const onClick = jest.fn()

const wrapper = mount(
<svg>
<AnimatedDataset
tag="rect"
dataset={dataset}
attrs={attrs}
key={p => p.x}
events={{ onClick }}
disableAnimation
/>
</svg>
)

expect(onClick).toBeCalledTimes(0)

const index = 1
const firstCircle = wrapper
.find('g')
.getDOMNode()
.getElementsByTagName('rect')
.item(index)
dispatch(firstCircle, new Event('click'))

expect(onClick).toBeCalledTimes(1)

const callArguments = onClick.mock.calls[0]
expect(callArguments[0]).toEqual(dataset[index])
expect(callArguments[1]).toEqual(index)
})
})
17 changes: 17 additions & 0 deletions tests/mapKey.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { mapKeys } from '../src/mapKey'

describe(mapKeys, () => {
it('should return new object with new keys', () => {
const ob = { x: 1, y: 2 }
const res = mapKeys(ob, key => key.toUpperCase())

expect(res).toEqual({ X: 1, Y: 2 })
})

it('should return overrided duplicated keys', () => {
const ob = { x: 1, y: 2 }
const res = mapKeys(ob, _ => 'z')

expect(res).toEqual({ z: 2 })
})
})
24 changes: 24 additions & 0 deletions tests/parse.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { parseAttributeName, parseEventName } from '../src/parse'

describe(parseAttributeName, () => {
it('should convert camel case to kebab case', () => {
expect(parseAttributeName('fontSize')).toEqual('font-size')
expect(parseAttributeName('HTMLInputElement')).toEqual('html-input-element')
})

it('should keep numbers', () => {
expect(parseAttributeName('x1')).toEqual('x1')
})
})

describe(parseEventName, () => {
it('should convert camel case to kebab case', () => {
expect(parseEventName('onClick')).toEqual('click')
expect(parseEventName('onMouseDown')).toEqual('mousedown')
})

it('should convert camel case to kebab case', () => {
expect(parseEventName('click')).toEqual('click')
expect(parseEventName('mousedown')).toEqual('mousedown')
})
})
Loading

0 comments on commit 59d21f1

Please sign in to comment.