(function(root, factory) { if (typeof module === 'object' && module.exports) { var d3 = require('d3'); module.exports = factory(d3); } else if(typeof define === 'function' && define.amd) { try { var d3 = require('d3'); } catch (e) { d3 = root.d3; } d3.contextMenu = factory(d3); define([], function() { return d3.contextMenu; }); } else if(root.d3) { root.d3.contextMenu = factory(root.d3); } }(this, function (d3) { var utils = { noop: function () {}, /** * @param {*} value * @returns {Boolean} */ isFn: function (value) { return typeof value === 'function'; }, /** * @param {*} value * @returns {Function} */ const: function (value) { return function () { return value; }; }, /** * @param {Function|*} value * @param {*} [fallback] * @returns {Function} */ toFactory: function (value, fallback) { value = (value === undefined) ? fallback : value; return utils.isFn(value) ? value : utils.const(value); } }; // global state for d3-context-menu var d3ContextMenu = null; var closeMenu = function () { // global state is populated if a menu is currently opened if (d3ContextMenu) { d3.select('.d3-context-menu').remove(); d3.select('body').on('mousedown.d3-context-menu', null); d3ContextMenu.boundCloseCallback(); d3ContextMenu = null; } }; /** * Calls API method (e.g. `close`) or * returns handler function for the `contextmenu` event * @param {Function|Array|String} menuItems * @param {Function|Object} config * @returns {?Function} */ return function (menuItems, config) { // allow for `d3.contextMenu('close');` calls // to programatically close the menu if (menuItems === 'close') { return closeMenu(); } // for convenience, make `menuItems` a factory // and `config` an object menuItems = utils.toFactory(menuItems); if (utils.isFn(config)) { config = { onOpen: config }; } else { config = config || {}; } // resolve config var openCallback = config.onOpen || utils.noop; var closeCallback = config.onClose || utils.noop; var positionFactory = utils.toFactory(config.position); var themeFactory = utils.toFactory(config.theme, 'd3-context-menu-theme'); /** * Context menu event handler * @param {*} data * @param {Number} index */ return function (data, index) { var element = this; // close any menu that's already opened closeMenu(); // store close callback already bound to the correct args and scope d3ContextMenu = { boundCloseCallback: closeCallback.bind(element, data, index) }; // create the div element that will hold the context menu d3.selectAll('.d3-context-menu').data([1]) .enter() .append('div') .attr('class', 'd3-context-menu ' + themeFactory.bind(element)(data, index)); // close menu on mousedown outside d3.select('body').on('mousedown.d3-context-menu', closeMenu); var list = d3.selectAll('.d3-context-menu') .on('contextmenu', function() { closeMenu(); d3.event.preventDefault(); d3.event.stopPropagation(); }) .append('ul'); list.selectAll('li').data(menuItems.bind(element)(data, index)).enter() .append('li') .attr('class', function(d) { var ret = ''; if (utils.toFactory(d.divider).bind(element)(data, index)) { ret += ' is-divider'; } if (utils.toFactory(d.disabled).bind(element)(data, index)) { ret += ' is-disabled'; } if (!d.action) { ret += ' is-header'; } return ret; }) .html(function(d) { if (utils.toFactory(d.divider).bind(element)(data, index)) { return '