diff --git a/spec/parser/javascript/repeat_spec.js b/spec/parser/javascript/repeat_spec.js index 147145d..32d35c0 100644 --- a/spec/parser/javascript/repeat_spec.js +++ b/spec/parser/javascript/repeat_spec.js @@ -265,6 +265,74 @@ describe('parser/javascript/repeat.js', function() { }); + describe('tooltip property', function() { + + beforeEach(function() { + this.node = new javascript.Parser('*').__consume__repeat(); + }); + + _.each([ + { + minimum: 1, + maximum: -1, + tooltip: undefined + }, + { + minimum: 0, + maximum: 0, + tooltip: undefined + }, + { + minimum: 2, + maximum: -1, + tooltip: 'repeats 2+ times in total' + }, + { + minimum: 3, + maximum: -1, + tooltip: 'repeats 3+ times in total' + }, + { + minimum: 0, + maximum: 2, + tooltip: 'repeats at most 2 times in total' + }, + { + minimum: 0, + maximum: 3, + tooltip: 'repeats at most 3 times in total' + }, + { + minimum: 2, + maximum: 2, + tooltip: 'repeats 2 times in total' + }, + { + minimum: 3, + maximum: 3, + tooltip: 'repeats 3 times in total' + }, + { + minimum: 2, + maximum: 3, + tooltip: 'repeats 2\u20263 times in total' + }, + { + minimum: 3, + maximum: 4, + tooltip: 'repeats 3\u20264 times in total' + } + + ], t => { + it(`is "${t.tooltip}" when minimum=${t.minimum} and maximum=${t.maximum}`, function() { + this.node.minimum = t.minimum; + this.node.maximum = t.maximum; + expect(this.node.tooltip).toEqual(t.tooltip); + }); + }); + + }); + describe('#skipPath', function() { beforeEach(function() { diff --git a/src/js/parser/javascript/match_fragment.js b/src/js/parser/javascript/match_fragment.js index 1ae45f8..cf38152 100644 --- a/src/js/parser/javascript/match_fragment.js +++ b/src/js/parser/javascript/match_fragment.js @@ -53,12 +53,19 @@ export default { // be matched. loopLabel() { var labelStr = this.repeat.label, - label, labelBox, box; + tooltipStr = this.repeat.tooltip, + label, tooltip, labelBox, box; if (labelStr) { label = this.container.text(0, 0, [labelStr]) .addClass('repeat-label'); + if (tooltipStr) { + tooltip = Snap().el('title') + .append(this.container.text(0, 0, tooltipStr)); + label.append(tooltip); + } + box = this.getBBox(); labelBox = label.getBBox(); label.transform(Snap.matrix().translate( diff --git a/src/js/parser/javascript/repeat.js b/src/js/parser/javascript/repeat.js index 715d888..613f42f 100644 --- a/src/js/parser/javascript/repeat.js +++ b/src/js/parser/javascript/repeat.js @@ -47,6 +47,31 @@ export default { } } } + }, + + // Tooltip to place of loop path label to provide further details. + tooltip: { + get: function() { + let repeatCount; + if (this.minimum === this.maximum) { + if (this.minimum === 0) { + repeatCount = undefined; + } else { + repeatCount = formatTimes(this.minimum); + } + } else if (this.minimum <= 1 && this.maximum >= 2) { + repeatCount = `at most ${formatTimes(this.maximum)}`; + } else if (this.minimum >= 2) { + if (this.maximum === -1) { + repeatCount = `${this.minimum}+ times`; + } else { + repeatCount = `${this.minimum}\u2026${formatTimes(this.maximum)}`; + } + } + + console.log(repeatCount, this.minimum, this.maximum); + return repeatCount ? `repeats ${repeatCount} in total` : repeatCount; + } } }, diff --git a/src/sass/svg.scss b/src/sass/svg.scss index 1316adc..5325ebb 100644 --- a/src/sass/svg.scss +++ b/src/sass/svg.scss @@ -50,6 +50,10 @@ circle { font-size: 10px; } +.repeat-label { + cursor: help; +} + .subexp .subexp-label tspan, .charset .charset-label tspan { dominant-baseline: text-after-edge;