curvy-tabs
Advanced tools
Comparing version 1.0.0 to 2.0.0
203
index.js
@@ -7,17 +7,20 @@ /* eslint-env browser */ | ||
var stylesheet = | ||
'.curvy-tabs-container {' + | ||
'position: absolute;' + | ||
'z-index: 0;' + | ||
'.curvy-tabs-container {' + | ||
'position: relative;' + | ||
// 'z-index: 0;' + | ||
'width: 500px;' + | ||
'height: 500px;' + | ||
'}\n' + | ||
'.curvy-tabs-container > div {' + | ||
'position: absolute;' + | ||
'left: 0;' + | ||
'bottom: 0;' + | ||
'right: 0;' + | ||
'border: 1px solid #aaa;' + | ||
'border-radius: 7px;' + | ||
'}\n' + | ||
'.curvy-tabs-bar {' + | ||
'position: relative;' + | ||
'z-index: 1;' + | ||
'}\n' + | ||
'.curvy-tabs-content {' + | ||
'.curvy-tabs-container > div > * {' + | ||
'position: absolute;' + | ||
'display: block;' + | ||
'visibility: hidden;' + | ||
'position: absolute;' + | ||
'padding: 8px;' + | ||
@@ -28,3 +31,7 @@ 'top: 0;' + | ||
'right: 0;' + | ||
'overflow: scroll;border-radius:7px' + | ||
'overflow: scroll;' + | ||
'border-radius: 7px;' + | ||
'}\n' + | ||
'.curvy-tabs-container > canvas {' + | ||
'position: absolute;' + | ||
'}\n'; | ||
@@ -34,10 +41,9 @@ | ||
var TRANSPARENT = 'rgba(0, 0, 0, 0)'; | ||
function CurvyTabs(container, selectedContentElement) { | ||
if (!document.head.querySelector('style#injected-stylesheet-curvy-tabs')) { | ||
var el = document.createElement('style'); | ||
el.id = 'injected-stylesheet-curvy-tabs'; | ||
el.innerHTML = stylesheet; | ||
document.head.insertBefore(el, document.head.firstElementChild); | ||
} | ||
injectStylesheet(); | ||
var children = Array.prototype.slice.call(container.children); | ||
this.container = container; | ||
@@ -47,6 +53,12 @@ this._minWidth = 0; | ||
this._font = '10pt sans-serif'; | ||
this._selected = selectedContentElement || container.querySelector('.curvy-tabs-content'); | ||
this._selected = selectedContentElement || children[0]; | ||
var contents = this.contents = document.createElement('div'); | ||
children.forEach(function(child) { | ||
contents.appendChild(child); | ||
}); | ||
container.appendChild(contents); | ||
var tabs = this.tabs = new WeakMap; | ||
getContentsArray.call(this).forEach(function(content) { | ||
this.contentDivs.forEach(function(content) { | ||
tabs.set(content, {}); | ||
@@ -57,10 +69,8 @@ }); | ||
var containerRect = container.getBoundingClientRect(); | ||
var canvas = this.canvas = document.createElement('canvas'); | ||
var containerRect = container.getBoundingClientRect(); | ||
document.body.insertBefore(canvas, container); | ||
canvas.style.left = containerRect.left - canvas.getBoundingClientRect().left + 'px'; | ||
canvas.classList.add('curvy-tabs-bar'); | ||
container.appendChild(canvas); | ||
canvas.width = containerRect.width; | ||
this.containerHeight = this.containerHeight; // sets canvas bottom margin | ||
this.height = CurvyTabs.height; // invokes paint() | ||
this.size = CurvyTabs.size; | ||
this.height = this.height; // invoke setter to calculate and set content height; invokes paint() | ||
@@ -70,3 +80,3 @@ this.canvas.addEventListener('click', clickHandler.bind(this)); | ||
CurvyTabs.height = 29; | ||
CurvyTabs.size = 29; | ||
@@ -76,20 +86,21 @@ CurvyTabs.prototype = { | ||
destruct: function() { | ||
this.canvas.remove(); | ||
this.container.style.position = 'static'; | ||
}, | ||
addTab: function(name, html, color) { | ||
var content = document.createElement('div'); | ||
content.name = name; | ||
content.className = 'curvy-tabs-content'; | ||
content.setAttribute('name', name); | ||
if (html) { | ||
content.innerHTML = html; | ||
} | ||
if (color) { | ||
content.style.backgroundColor = color; | ||
} | ||
content.innerHTML = html; | ||
this.tabs.set(content, {}); | ||
this.container.appendChild(content); | ||
this.contents.appendChild(content); | ||
this.selected = content; | ||
this.paint(); | ||
}, | ||
get contentDivs() { | ||
return Array.prototype.slice.call(this.contents.children); | ||
}, | ||
get minWidth() { | ||
@@ -112,18 +123,17 @@ return this._minWidth; | ||
get height() { | ||
return this.height; | ||
get size() { | ||
return this.canvas.height; | ||
}, | ||
set height(height) { | ||
this.canvas.height = height; | ||
this.container.style.top = window.scrollY + this.canvas.getBoundingClientRect().bottom - 1 + 'px'; | ||
set size(size) { | ||
this.canvas.height = size; | ||
this.contents.style.top = size - 1 + 'px'; | ||
this.paint(); | ||
}, | ||
get containerHeight() { | ||
get height() { | ||
return this.container.getBoundingClientRect().height; | ||
}, | ||
set containerHeight(height) { | ||
var style = window.getComputedStyle(this.container); | ||
set height(height) { | ||
this.container.style.height = height + 'px'; | ||
this.canvas.style.marginBottom = parseInt(style.height) + parseInt(style.marginBottom) + 'px'; | ||
this.contents.style.top = this.size - borderWidth.call(this) + 'px'; | ||
this.paint(); | ||
@@ -138,3 +148,3 @@ }, | ||
this.paint(); | ||
this.container.style.borderRadius = curviness * 7 + 'px'; | ||
this.contents.style.borderRadius = curviness * 7 + 'px'; | ||
setContentsBorderRadius.call(this); | ||
@@ -151,10 +161,2 @@ }, | ||
get borderColor() { | ||
return window.getComputedStyle(this.container).borderTopColor; | ||
}, | ||
set borderColor(color) { | ||
tabBar.container.style.borderColor = color; | ||
tabBar.paint(); | ||
}, | ||
get selected() { | ||
@@ -168,2 +170,13 @@ return this._selected; | ||
css: function(keyOrObject, value) { | ||
css.call(this, this.contents, keyOrObject, value); | ||
this.height = this.height; // invoke setter to reset overlap per possible border width change; invokes paint() | ||
}, | ||
contentCss: function(keyOrObject, value) { | ||
this.contentDivs.forEach(function(content) { | ||
css.call(this, content, keyOrObject, value); | ||
}); | ||
}, | ||
paint: function() { | ||
@@ -174,6 +187,6 @@ var x = 8; | ||
this.gc.textAlign = 'center'; | ||
this.gc.clearRect(0, 0, this.canvas.width, this.canvas.height); | ||
this.gc.clearRect(0, 0, this.canvas.width, this.size); | ||
this.gc.font = this.font; | ||
var contents = getContentsArray.call(this); | ||
var contents = this.contentDivs; | ||
if (contents.length) { | ||
@@ -183,3 +196,3 @@ contents.forEach(function(content) { | ||
x += props.width = drawTab.call(this, content, props.left = x); | ||
x -= (0.80 - this.curviness * 0.11) * this.canvas.height; // overlap tabs | ||
x -= (0.80 - this.curviness * 0.11) * this.size; // overlap tabs | ||
}, this); | ||
@@ -192,9 +205,34 @@ drawTab.call(this, this.selected, this.tabs.get(this.selected).left, true); | ||
function getContentsArray() { | ||
return Array.prototype.slice.call(this.container.querySelectorAll('.curvy-tabs-content')); | ||
function css(el, keyOrObject, value) { | ||
var style = el.style; | ||
if (typeof keyOrObject === 'object' && !Array.isArray(keyOrObject)) { | ||
Object.keys(keyOrObject).forEach(function(key) { | ||
style[key] = keyOrObject[key]; | ||
}) | ||
} else if (arguments.length === 3) { | ||
style[keyOrObject] = value; | ||
} else if (arguments.length === 2) { | ||
if (Array.isArray(keyOrObject)) { | ||
return keyOrObject.reduce(function(dict, key) { | ||
dict[key] = style[key]; | ||
return dict; | ||
}, {}); | ||
} else { | ||
return window.getComputedStyle(el)[keyOrObject]; | ||
} | ||
} | ||
} | ||
function injectStylesheet() { | ||
if (!document.head.querySelector('style#injected-stylesheet-curvy-tabs')) { | ||
var el = document.createElement('style'); | ||
el.id = 'injected-stylesheet-curvy-tabs'; | ||
el.innerHTML = stylesheet; | ||
document.head.insertBefore(el, document.head.firstElementChild); | ||
} | ||
} | ||
function setContentsBorderRadius() { | ||
var borderRadius = this.container.style.borderRadius; | ||
getContentsArray.call(this).forEach(function(content) { | ||
var borderRadius = this.contents.style.borderRadius; | ||
this.contentDivs.forEach(function(content) { | ||
content.style.borderRadius = borderRadius; | ||
@@ -204,11 +242,28 @@ }); | ||
function contentStyle() { | ||
return window.getComputedStyle(this.contents); | ||
} | ||
function borderWidth() { | ||
return parseFloat(contentStyle.call(this).borderWidth); | ||
} | ||
function drawTab(content, x, onTop) { | ||
var gc = this.gc; | ||
var height = this.canvas.height - 1; | ||
var curveWidth = .5 * height; | ||
var size = this.size - 1; | ||
var curveWidth = .5 * size; | ||
var cw80 = this.curviness * 0.80 * curveWidth; | ||
var text = content.getAttribute('name'); | ||
var w = Math.max(this.minWidth, PADDING + gc.measureText(text).width + PADDING); | ||
var color = window.getComputedStyle(content).backgroundColor; | ||
for (var el = content, color = TRANSPARENT; color === TRANSPARENT; el = el.parentElement) { | ||
color = window.getComputedStyle(el).backgroundColor; | ||
if (el === document.body) { | ||
if (color === TRANSPARENT) { | ||
color = 'white'; | ||
} | ||
break; | ||
} | ||
} | ||
gc.save(); // Save current transformation | ||
@@ -218,6 +273,8 @@ | ||
gc.lineWidth = borderWidth.call(this); | ||
if (onTop) { | ||
gc.beginPath(); | ||
gc.moveTo(GAP, height); // Begin a new subpath there | ||
gc.lineTo(GAP + curveWidth + w + curveWidth, height); | ||
gc.moveTo(GAP, size); // Begin a new subpath there | ||
gc.lineTo(GAP + curveWidth + w + curveWidth, size); | ||
gc.strokeStyle = color; | ||
@@ -228,10 +285,10 @@ gc.stroke(); | ||
gc.beginPath(); | ||
gc.moveTo(0, height); // Begin a new subpath there | ||
gc.lineTo(GAP, height); | ||
gc.moveTo(0, size); // Begin a new subpath there | ||
gc.lineTo(GAP, size); | ||
gc.translate(GAP, 0); | ||
gc.bezierCurveTo(cw80, height, curveWidth - cw80, 0, curveWidth, 0); | ||
gc.bezierCurveTo(cw80, size, curveWidth - cw80, 0, curveWidth, 0); | ||
gc.lineTo(curveWidth + w, 0); | ||
gc.translate(curveWidth + w, 0); | ||
gc.bezierCurveTo(cw80, 0, curveWidth - cw80, height, curveWidth, height); | ||
gc.lineTo(curveWidth + GAP, height); | ||
gc.bezierCurveTo(cw80, 0, curveWidth - cw80, size, curveWidth, size); | ||
gc.lineTo(curveWidth + GAP, size); | ||
@@ -245,5 +302,5 @@ if (!onTop) { | ||
gc.fillStyle = 'black'; | ||
gc.fillText(text, -w / 2, 2 * height / 3); | ||
gc.fillText(text, -w / 2, 2 * size / 3); | ||
gc.strokeStyle = this.borderColor; | ||
gc.strokeStyle = contentStyle.call(this).borderColor; | ||
gc.stroke(); | ||
@@ -265,5 +322,5 @@ | ||
function clickHandler(event) { | ||
getContentsArray.call(this).find(function(content) { | ||
this.contentDivs.find(function(content) { | ||
var props = this.tabs.get(content); | ||
var margin = GAP + .25 * this.canvas.height; | ||
var margin = GAP + .25 * this.size; | ||
var left = props.left + margin; | ||
@@ -270,0 +327,0 @@ if (event.offsetX > left && event.offsetX < left + props.width - margin - margin) { |
{ | ||
"name": "curvy-tabs", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "Tab bar with fancy tabs", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -6,6 +6,6 @@ See the [demo](https://joneit.github.io/curvy-tabs/1.0.0). | ||
<div class="curvy-tabs-container"> | ||
<div class="curvy-tabs-content" style="background-color:lightblue" name="Tab A"> | ||
<div style="background-color:lightblue" name="Tab A"> | ||
Content for Tab A goes here. | ||
</div> | ||
<div class="curvy-tabs-content" style="background-color:lightgreen" name="Tab B"> | ||
<div style="background-color:lightgreen" name="Tab B"> | ||
Content for Tab B goes here. | ||
@@ -23,3 +23,3 @@ </div> | ||
``` | ||
The following instantiates the controller object and adds the tab bar (a `canvas` element) above the container element: | ||
The following instantiates the controller object, collecting all the content divs into a sub-div, and adds the tab bar (a `canvas` element) above it: | ||
```js | ||
@@ -32,6 +32,6 @@ var container = document.querySelector('.curvy-tabs-container'); // or whatever | ||
### API | ||
The first tab is selected by default. To programmatically specify some other tab, set `selected` to the content element: | ||
The first tab is selected by default. To programmatically specify some other tab, set `selected` to a specific content div, either of the following works: | ||
```js | ||
var tabB = document.getElementByClassName('curvy-tabs-content')[1]; | ||
tabBar.selected = tabB; // or whatever | ||
tabBar.selected = tabBar.contents.children[1]; | ||
tabBar.selected = tabBar.contents.querySelector('[name="Tab B"]'); | ||
``` | ||
@@ -58,13 +58,13 @@ To select an alternative tab on instantiation: | ||
_Before instantiation,_ reset the default height (29 pixels): | ||
_Before instantiation,_ reset the default tab size (initially 29 pixels): | ||
```js | ||
CurvyTabs.height = 40; | ||
CurvyTabs.size = 40; | ||
``` | ||
_After instantiation:_ | ||
```js | ||
tabBar.height = 40; | ||
tabBar.size = 40; | ||
``` | ||
The container _must_ have a width and height. The default is 500 × 500 pixels. | ||
_Before instantiation,_ use CSS (affects all instances): | ||
_Before instantiation,_ use CSS to change the default (affects all instances): | ||
```html | ||
@@ -75,8 +75,8 @@ <style> | ||
``` | ||
_After instantiation,_ container's width can be set programmatically: | ||
_After instantiation,_ an instance's container width and height can be set programmatically: | ||
```js | ||
tabBar.width = 750; // sets both the tab bar width and the container width | ||
tabBar.containerHeight = 1050; | ||
tabBar.height = 1050; | ||
``` | ||
To change the default border color (`#aaaaaa`): | ||
Background color, border color, and border width affect both the tab bar and content area and can be set as follows: | ||
@@ -86,16 +86,29 @@ _Before instantiation,_ use CSS (affects all instances): | ||
<style> | ||
.curvy-tabs-container { border-color: blue; } | ||
.curvy-tabs-container > div { | ||
border: 2x solid red; | ||
background-color: yellow; | ||
} | ||
</style> | ||
``` | ||
_After instantiation,_ can be set programmatically: | ||
_After instantiation,_ such styles can be set programmatically using the `css` method (works like [jQuery's `css` method](http://api.jquery.com/css/)): | ||
```js | ||
tabBar.container.style.borderColor = 'blue'; | ||
tabBar.paint(); | ||
tabBar.css('borderColor', 'red'); // sets border color | ||
tabBar.css('borderColor'); // returns border color | ||
tabBar.css({ borderColor: 'yellow', backgroundColor: 'red' }); // sets both style properties | ||
tabBar.css(['borderColor', 'backgroundColor']); // returns style dictionary | ||
``` | ||
To change the default padding (`8px`), use CSS (affects all instances): | ||
(Note that the content area background color serves as a default for transparent tabs; there is no point in setting this if all your tabs colors.) | ||
To set styles on all the content divs at once: | ||
_Before instantiation,_ use CSS (affects all instances): | ||
```html | ||
<style> | ||
.curvy-tabs-content { padding: 3px } | ||
.curvy-tabs-content > div > * { padding: 3px } | ||
</style> | ||
``` | ||
_After instantiation,_ use the `contentCss` method (also like jQuery's `css` method): | ||
```js | ||
tabBar.contentCss('padding', '2px'); | ||
``` | ||
### Event Handlers | ||
@@ -122,2 +135,10 @@ #### `tabBar.onclick` | ||
##### `event.stopPropagation()` | ||
The event will be propagated to the `tabBar.onclick` handler (if defined) unless you call `event.stopPropagation()` from within. | ||
The event will be propagated to the `tabBar.onclick` handler (if defined) unless you call `event.stopPropagation()` from within. | ||
## Version History | ||
* `1.0.0` — Initial version | ||
* `2.0.0` | ||
* Cleaner DOM structure | ||
* `height` is now `size` | ||
* `containerHeight` is now `height` | ||
* added `contents`, `contentDivs`, css`, and `contentCss` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
16491
284
137