diff --git a/README.md b/README.md index 20ad1189..932b3721 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Name|Type|Default|Description `onSelect`|`(select)=>{}`|`false`|When a function is passed in, clicking a value triggers the `onSelect` method to be called. `sortKeys`|`boolean`|`false`|set to true to sort object keys `quotesOnKeys`|`boolean`|`true`|set to false to remove quotes from keys (eg. `"name":` vs. `name:`) +`quotesOnValues`|`boolean`|`true`|set to false to remove quotes from Values (eg. `[key]: "value"` vs. `[key]: value`) `validationMessage`|`string`|"Validation Error"|Custom message for validation failures to `onEdit`, `onAdd`, or `onDelete` callbacks `displayArrayKey`|`boolean`|`true`|When set to `true`, the index of the elements prefix values @@ -145,4 +146,4 @@ For information about contributing with Docker, see the [README in ./docker](htt ### Inspiration I drew a ton of design ideas from [react-json-tree](https://github.com/alexkuz/react-json-tree). Thanks to the RJT contributors for putting together an awesome component! -I'm also inspired by users who come up with interesting feature requests. Reach out to me with ideas for this project or other projects you want to collaborate on. My email address is listed on my [github user page](https://github.com/mac-s-g). +I'm also inspired by users who come up with interesting feature requests. Reach out to me with ideas for this project or other projects you want to collaborate on. My email address is listed on my [github user page](https://github.com/mac-s-g). \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index c1a2ce31..7f6dcc39 100644 --- a/index.d.ts +++ b/index.d.ts @@ -93,6 +93,12 @@ export interface ReactJsonViewProps { * Default: true */ quotesOnKeys?: boolean; + /** + * set to false to remove quotes from Values (eg. [key]: "value" vs. [key]: value) + * + * Default: true + */ + quotesOnValues?: boolean; /** * When a callback function is passed in, edit functionality is enabled. * The callback is invoked before edits are completed. Returning false diff --git a/src/js/components/DataTypes/Function.js b/src/js/components/DataTypes/Function.js index 473afe9e..4f8dec69 100644 --- a/src/js/components/DataTypes/Function.js +++ b/src/js/components/DataTypes/Function.js @@ -4,6 +4,8 @@ import DataTypeLabel from './DataTypeLabel'; //theme import Theme from './../../themes/getStyle'; +import { generateExpanderProps } from '../../helpers/util'; + //attribute store for storing collapsed state import AttributeStore from './../../stores/ObjectAttributes'; @@ -48,7 +50,7 @@ export default class extends React.PureComponent { {this.getFunctionDisplay(collapsed)} diff --git a/src/js/components/DataTypes/Object.js b/src/js/components/DataTypes/Object.js index 4c7cb93f..ee23ab6b 100644 --- a/src/js/components/DataTypes/Object.js +++ b/src/js/components/DataTypes/Object.js @@ -1,6 +1,6 @@ import React from 'react'; import { polyfill } from 'react-lifecycles-compat'; -import { toType } from './../../helpers/util'; +import { toType, generateExpanderProps } from './../../helpers/util'; //data type components import { JsonObject } from './DataTypes'; @@ -156,10 +156,12 @@ class RjvObject extends React.PureComponent { return ( { - this.toggleCollapsed(); - }} + {...generateExpanderProps( + this.toggleCollapsed, + this.state.expanded + )} {...Theme(theme, 'brace-row')} + className="rjv-object-container" >
- "{value}" + {quotesOnValues ? '"' : ''} + {value} + {quotesOnValues ? '"' : ''}
); diff --git a/src/js/helpers/util.js b/src/js/helpers/util.js index 50ab5185..58d067e8 100644 --- a/src/js/helpers/util.js +++ b/src/js/helpers/util.js @@ -53,3 +53,20 @@ export function isTheme(theme) { } return false; } + +export function generateExpanderProps(callback, isExpanded) { + function handler(e) { + if (e && e.key && e.key != 'Enter') { + return; + } else { + return callback(e); + } + } + return { + onClick: callback, + onKeyDown: handler, + role: 'button', + tabIndex: '0', + 'aria-expanded': isExpanded + }; +} diff --git a/src/js/index.js b/src/js/index.js index f47338df..0e4142a8 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -43,6 +43,7 @@ class ReactJsonView extends React.PureComponent { shouldCollapse: false, sortKeys: false, quotesOnKeys: true, + quotesOnValues: true, groupArraysAfterLength: 100, indentWidth: 4, enableClipboard: true, diff --git a/test/tests/js/components/DataTypes/Function-test.js b/test/tests/js/components/DataTypes/Function-test.js index 2207dd22..d127bb6a 100644 --- a/test/tests/js/components/DataTypes/Function-test.js +++ b/test/tests/js/components/DataTypes/Function-test.js @@ -1,86 +1,120 @@ -import React from "react" -import { shallow, mount } from "enzyme" -import { expect } from "chai" +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import { expect } from 'chai'; -import JsonFunction from "./../../../../../src/js/components/DataTypes/Function" +import JsonFunction from './../../../../../src/js/components/DataTypes/Function'; -import AttributeStore from "./../../../../../src/js/stores/ObjectAttributes" +import AttributeStore from './../../../../../src/js/stores/ObjectAttributes'; -describe("", function() { - const rjvId = 1 +describe('', function () { + const rjvId = 1; - it("function component should have a data type label", function() { + it('function component should have a data type label', function () { const wrapper = mount( - ) - expect(wrapper.find(".data-type-label")).to.have.length(1) - }) + ); + expect(wrapper.find('.data-type-label')).to.have.length(1); + }); - it("function component should not have a data type label", function() { + it('function component should not have a data type label', function () { const wrapper = mount( - ) - expect(wrapper.find(".data-type-label")).to.have.length(0) - }) + ); + expect(wrapper.find('.data-type-label')).to.have.length(0); + }); - it("function component expanded", function() { - AttributeStore.set(rjvId, "function-test", "collapsed", false) + it('function component expanded', function () { + AttributeStore.set(rjvId, 'function-test', 'collapsed', false); const wrapper = shallow( - ) - expect(wrapper.find(".function-collapsed")).to.have.length(0) - }) + ); + expect(wrapper.find('.function-collapsed')).to.have.length(0); + }); - it("function component collapsed", function() { - AttributeStore.set(rjvId, "function-test", "collapsed", true) + it('function component collapsed', function () { + AttributeStore.set(rjvId, 'function-test', 'collapsed', true); const wrapper = shallow( - ) + ); - expect(wrapper.find(".function-collapsed")).to.have.length(1) - }) + expect(wrapper.find('.function-collapsed')).to.have.length(1); + }); - it("function component click to expand", function() { - AttributeStore.set(rjvId, "function-test", "collapsed", true) + it('function component click to expand', function () { + AttributeStore.set(rjvId, 'function-test', 'collapsed', true); const wrapper = shallow( - ) + ); - expect(wrapper.find(".function-collapsed")).to.have.length(1) + expect(wrapper.find('.function-collapsed')).to.have.length(1); + expect( + wrapper.find('.rjv-function-container').prop('aria-expanded') + ).to.equal(false); - wrapper.find(".rjv-function-container").simulate("click") + wrapper.find('.rjv-function-container').simulate('click'); - expect(wrapper.find(".function-collapsed")).to.have.length(0) - }) -}) + expect(wrapper.find('.function-collapsed')).to.have.length(0); + expect( + wrapper.find('.rjv-function-container').prop('aria-expanded') + ).to.equal(true); + }); + + it('function component keydown to expand', function () { + AttributeStore.set(rjvId, 'function-test', 'collapsed', true); + + const wrapper = shallow( + + ); + + expect(wrapper.find('.function-collapsed')).to.have.length(1); + expect( + wrapper.find('.rjv-function-container').prop('aria-expanded') + ).to.equal(false); + + wrapper + .find('.rjv-function-container') + .simulate('keydown', { key: 'Enter' }); + + expect(wrapper.find('.function-collapsed')).to.have.length(0); + expect( + wrapper.find('.rjv-function-container').prop('aria-expanded') + ).to.equal(true); + }); +}); diff --git a/test/tests/js/components/DataTypes/Object-test.js b/test/tests/js/components/DataTypes/Object-test.js index d50c8d7d..cf669248 100644 --- a/test/tests/js/components/DataTypes/Object-test.js +++ b/test/tests/js/components/DataTypes/Object-test.js @@ -5,7 +5,11 @@ import { expect } from 'chai'; import JsonObject from './../../../../../src/js/components/DataTypes/Object'; describe('', function () { - const rjvId = 1; + let rjvId = 1; + + beforeEach(() => { + rjvId++; + }); it('Object component should have a data type label', function () { let src = { @@ -373,4 +377,52 @@ describe('', function () { ); expect(wrapper.text()).to.equal('"":{"d":"d""b":"b""a":"a""c":"c"}'); }); + + it('Object click to expand', function () { + let src = { + obj: { + test: true + } + }; + const wrapper = shallow( + + ); + expect( + wrapper.find('.rjv-object-container').prop('aria-expanded') + ).to.equal(false); + wrapper.find('.rjv-object-container').simulate('click'); + expect( + wrapper.find('.rjv-object-container').prop('aria-expanded') + ).to.equal(true); + }); + + it('Object keydown to expand', function () { + let src = { + obj: { + test: true + } + }; + const wrapper = shallow( + + ); + + expect( + wrapper.find('.rjv-object-container').prop('aria-expanded') + ).to.equal(false); + + wrapper + .find('.rjv-object-container') + .simulate('keydown', { key: 'Space' }); + + expect( + wrapper.find('.rjv-object-container').prop('aria-expanded') + ).to.equal(false); + + wrapper + .find('.rjv-object-container') + .simulate('keydown', { key: 'Enter' }); + expect( + wrapper.find('.rjv-object-container').at(0).prop('aria-expanded') + ).to.equal(true); + }); }); diff --git a/test/tests/js/components/DataTypes/String-test.js b/test/tests/js/components/DataTypes/String-test.js index 44c1f0ac..bdd24e73 100644 --- a/test/tests/js/components/DataTypes/String-test.js +++ b/test/tests/js/components/DataTypes/String-test.js @@ -1,12 +1,12 @@ -import React from "react" -import { shallow, mount } from "enzyme" -import { expect } from "chai" +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import { expect } from 'chai'; -import JsonString from "./../../../../../src/js/components/DataTypes/String" +import JsonString from './../../../../../src/js/components/DataTypes/String'; -describe("", function() { - it("string component should have a data type label", function() { - const rjvId = 1 +describe('', function () { + it('string component should have a data type label', function () { + const rjvId = 1; const wrapper = mount( ", function() { displayDataTypes={true} theme="rjv-default" /> - ) - expect(wrapper.find(".data-type-label")).to.have.length(1) - }) + ); + expect(wrapper.find('.data-type-label')).to.have.length(1); + }); - it("string with hidden data type", function() { - const rjvId = 1 + it('string with hidden data type', function () { + const rjvId = 1; const props = { - value: "test", + value: 'test', rjvId: 1, - theme: "rjv-default", + theme: 'rjv-default', displayDataTypes: false - } - const component = mount().render() - expect(component.find(".data-type-label")).to.have.length(0) - }) + }; + const component = mount().render(); + expect(component.find('.data-type-label')).to.have.length(0); + }); //test collapsed string and expand click - it("string displaying data type", function() { - const rjvId = 1 + it('string displaying data type', function () { + const rjvId = 1; const props = { - value: "test", + value: 'test', rjvId: 1, displayDataTypes: false, - theme: "rjv-default" - } - const component = mount().render() - expect(component.find(".data-type-label")).to.have.length(0) - }) + theme: 'rjv-default' + }; + const component = mount().render(); + expect(component.find('.data-type-label')).to.have.length(0); + }); - it("collapsed string content", function() { - const rjvId = 1 + it('collapsed string content', function () { const props = { - value: "123456789", + value: '123456789', collapseStringsAfterLength: 3, rjvId: 1, displayDataTypes: false, - theme: "rjv-default" - } - const component = shallow() - expect( - component - .render() - .find(".string-value") - .text() - ).to.equal('"123 ..."') - component.find(".string-value").simulate("click") - expect( - component - .render() - .find(".string-value") - .text() - ).to.equal('"123456789"') - }) -}) + theme: 'rjv-default' + }; + const component = shallow(); + expect(component.render().find('.string-value').text()).to.equal( + '"123 ..."' + ); + component.find('.string-value').simulate('click'); + expect(component.render().find('.string-value').text()).to.equal( + '"123456789"' + ); + }); + + it('remove quotes from string value', function () { + const props = { + value: '123456789', + quotesOnValues: false, + rjvId: 1, + displayDataTypes: false, + theme: 'rjv-default' + }; + const component = shallow(); + expect(component.render().find('.string-value').text()).to.equal( + '123456789' + ); + }); + + it('collapsed string content with key handler', function () { + const props = { + value: '123456789', + collapseStringsAfterLength: 3, + rjvId: 2, + displayDataTypes: false, + theme: 'rjv-default' + }; + const component = shallow(); + expect(component.render().find('.string-value').text()).to.equal( + '"123 ..."' + ); + + component.find('.string-value').simulate('keydown', { key: 'Space' }); + expect(component.render().find('.string-value').text()).to.equal( + '"123 ..."' + ); + + component.find('.string-value').simulate('keydown', { key: 'Enter' }); + expect(component.render().find('.string-value').text()).to.equal( + '"123456789"' + ); + }); +});