diff --git a/CHANGELOG.md b/CHANGELOG.md index e5d439453..c550c5a8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ -## Unreleased +## 0.3.0 + +### Improvements +- Removed ~ 230 LOC from calcite-web.js ### Fixed - Fixed interpolation of variables in the `keyframes` mixin +- Fixed modals always closing on any click if open +- Document modals properly -### Modifide +### Modified - Increase large breakpoint to 1450px - add `extra-large-leader-n` - trailer @@ -13,6 +18,9 @@ - Remove `container-max` and `container-min` - `extra-large-hide` and `-only` +### Removed +- remove carousel pattern (will be its own project) + ## 0.2.3 ### Modified diff --git a/docs/source/patterns/_carousel.md b/docs/source/patterns/_carousel.md deleted file mode 100644 index 355dfa7ed..000000000 --- a/docs/source/patterns/_carousel.md +++ /dev/null @@ -1,9 +0,0 @@ -## Carousel - -Calcite Web comes with a very lightweight carousel (or slider). We urge you to use carousels very sparingly as [several](http://erikrunyon.com/2013/07/carousel-interaction-stats/) [reports](http://www.nngroup.com/articles/auto-forwarding/) [show](http://www.widerfunnel.com/conversion-rate-optimization/rotating-offers-the-scourge-of-home-page-design) that carousels can be detrimental to user experience. - -If you decide a carousel *is* the best experience for your use case, the Calcite Web carousel is very straightforward. Simply create a div with the class of `carousel` with any number of elements that have a class of `carousel-slide`. The carousel uses [calcite-web.js](../javascript) for the interactive elements. For this reason you'll also need to add a class of `js-carousel` to add JavaScript functionality. - -Navigation for the carousel can be any link inside the carousel wrapper. Simply add a `js-carousel-link` class and a `data-slide` attribute with a number representing the slide number the link should navigate to. Numbering starts at 1. - -> Note: it's recommended to set a height on the `carousel-slides` element to make sure it will fit your content vertically. \ No newline at end of file diff --git a/docs/source/patterns/_modals.md b/docs/source/patterns/_modals.md index 931e720b1..361e63784 100644 --- a/docs/source/patterns/_modals.md +++ b/docs/source/patterns/_modals.md @@ -1,3 +1,7 @@ ## Modals -Modals are meant to "take over" the screen and focus users attention on a dialog which presents the user with an opportunity to add, modify or create content. A modal should always be centered both vertically and horizontally within the browser window. When a modal is opened, the interface darkens and disables all other user interface elements in order to force a user to take an action required by their workflow. \ No newline at end of file +Modals are meant to "take over" the screen and focus users attention on a dialog which presents the user with an opportunity to add, modify or create content. A modal should always be centered both vertically and horizontally within the browser window. When a modal is opened, the interface darkens and disables all other user interface elements in order to force a user to take an action required by their workflow. Two modals can't be open at once. + +To create a link or button that opens a modal, you must add a `js-modal-toggle` class to the element, along with a `data-modal` attribute specifying the name of the modal that should open. The modal should also get a `data-modal` attribute with the same name. + +Elements with the `js-modal-toggle` that are inside a modal don't need the `data-modal` attribute as they will just close the modal they are in. diff --git a/docs/source/patterns/sample-code/_carousel.html b/docs/source/patterns/sample-code/_carousel.html deleted file mode 100644 index da582ade7..000000000 --- a/docs/source/patterns/sample-code/_carousel.html +++ /dev/null @@ -1,35 +0,0 @@ - \ No newline at end of file diff --git a/docs/source/patterns/sample-code/_modals.html b/docs/source/patterns/sample-code/_modals.html index 8fd3e56c4..2d8336052 100644 --- a/docs/source/patterns/sample-code/_modals.html +++ b/docs/source/patterns/sample-code/_modals.html @@ -9,9 +9,9 @@

Modal!

cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

- - + + -Show Modal +Show Modal diff --git a/docs/source/table_of_contents.yml b/docs/source/table_of_contents.yml index 8df607884..b3c2cf45e 100644 --- a/docs/source/table_of_contents.yml +++ b/docs/source/table_of_contents.yml @@ -515,10 +515,6 @@ patterns: id: '93e95ff8-0ad4-478b-b1c4-d87a34486b05' link: accordions modifiers: true - - title: 'Carousels' - id: 'c5ffc172-f526-4952-a18f-9f06a7d9724c' - link: carousel - modifiers: true - title: 'Drawers' id: 'e538b8cd-ce5e-4f86-823a-ebb5ec041039' link: drawers diff --git a/lib/js/calcite-web.js b/lib/js/calcite-web.js index 57563e3c0..53971035f 100644 --- a/lib/js/calcite-web.js +++ b/lib/js/calcite-web.js @@ -1,668 +1,438 @@ (function Calcite () { -var calcite = { - version: '0.0.9' -}; - -// ┌───────────────┐ -// │ DOM Utilities │ -// └───────────────┘ - -calcite.dom = {}; - -// ┌──────────────────────┐ -// │ DOM Event Management │ -// └──────────────────────┘ - -// returns standard interaction event, later will add touch support -calcite.dom.event = function () { - return 'click'; -}; - -// add a callback function to an event on a DOM node -calcite.dom.addEvent = function (domNode, event, fn) { - if (domNode.addEventListener) { - return domNode.addEventListener(event, fn, false); - } - if (domNode.attachEvent) { - return domNode.attachEvent('on' + event, fn); - } -}; - -// remove a specific function binding from a DOM node event -calcite.dom.removeEvent = function (domNode, event, fn) { - if (domNode.removeEventListener) { - return domNode.removeEventListener(event, fn, false); - } - if (domNode.detachEvent) { - return domNode.detachEvent('on' + event, fn); - } -}; - -// get the target element of an event -calcite.dom.eventTarget = function (event) { - if (!event.target) { - return event.srcElement; - } - if (event.target) { - return event.target; - } -}; - -// prevent default behavior of an event -calcite.dom.preventDefault = function (event) { - if (event.preventDefault) { - return event.preventDefault(); - } - if (event.returnValue) { - event.returnValue = false; - } -}; - -// stop and event from bubbling up the DOM tree -calcite.dom.stopPropagation = function (event) { - event = event || window.event; - if (event.stopPropagation) { - return event.stopPropagation(); - } - if (event.cancelBubble) { - event.cancelBubble = true; - } -}; + // ┌────────────┐ + // │ Public API │ + // └────────────┘ + // define all public api methods (excluding patterns) + var calcite = { + version: 'v0.2.2', + click: click, + addEvent: addEvent, + removeEvent: removeEvent, + eventTarget: eventTarget, + preventDefault: preventDefault, + stopPropagation: stopPropagation, + hasClass: hasClass, + addClass: addClass, + removeClass: removeClass, + closest: closest, + getAttr: getAttr, + nodeListToArray: nodeListToArray, + patterns: {}, + init: init + }; -// ┌────────────────────┐ -// │ Class Manipulation │ -// └────────────────────┘ + // ┌──────────────────────┐ + // │ DOM Event Management │ + // └──────────────────────┘ -// check if an element has a specific class -calcite.dom.hasClass = function (domNode, className) { - var exp = new RegExp(' ' + className + ' '); - if (exp.test(' ' + domNode.className + ' ')) { - return true; + // returns standard interaction event, later will add touch support + function click () { + return 'click'; } - return false; -}; - -// add one or more classes to an element -calcite.dom.addClass = function (domNode, classes) { - classes = classes.split(' '); - - for (var i = 0; i < classes.length; i++) { - if (!calcite.dom.hasClass(domNode, classes[i])) { - domNode.className += ' ' + classes[i]; + // add a callback function to an event on a DOM node + function addEvent (domNode, e, fn) { + if (domNode.addEventListener) { + return domNode.addEventListener(e, fn, false); + } else if (domNode.attachEvent) { + return domNode.attachEvent('on' + e, fn); } } -}; - -// remove one or more classes from an element -calcite.dom.removeClass = function (domNode, classes) { - classes = classes.split(' '); - - for (var i = 0; i < classes.length; i++) { - var newClass = ' ' + domNode.className.replace( /[\t\r\n]/g, ' ') + ' '; - if (calcite.dom.hasClass(domNode, classes[i])) { - while (newClass.indexOf(' ' + classes[i] + ' ') >= 0) { - newClass = newClass.replace(' ' + classes[i] + ' ', ' '); - } - - domNode.className = newClass.replace(/^\s+|\s+$/g, ''); + // remove a specific function binding from a DOM node event + function removeEvent (domNode, e, fn) { + if (domNode.removeEventListener) { + return domNode.removeEventListener(e, fn, false); + } else if (domNode.detachEvent) { + return domNode.detachEvent('on' + e, fn); } } -}; - -// ┌───────────────┐ -// │ DOM Traversal │ -// └───────────────┘ - -// returns closest element up the DOM tree matching a given class -calcite.dom.closest = function (className, context) { - var result, current; - for (current = context; current; current = current.parentNode) { - if (current.nodeType === 1 && calcite.dom.hasClass(current, className)) { - result = current; - break; - } - } - return current; -}; -// get an attribute for an element -calcite.dom.getAttr = function(domNode, attr) { - if (domNode.getAttribute) { - return domNode.getAttribute(attr); + // get the target element of an event + function eventTarget (e) { + return e.target || e.srcElement; } - var result; - var attrs = domNode.attributes; - - for (var i = 0; i < attrs.length; i++) { - if (attrs[i].nodeName === attr) { - result = attrs[i].nodeValue; + // prevent default behavior of an event + function preventDefault (e) { + if (e.preventDefault) { + return e.preventDefault(); + } else if (e.returnValue) { + e.returnValue = false; } } - return result; -}; - -// ┌───────────────────┐ -// │ Object Conversion │ -// └───────────────────┘ - -// turn a domNodeList into an array -calcite.dom.nodeListToArray = function (domNodeList) { - var array = []; - for (var i = 0; i < domNodeList.length; i++) { - array.push(domNodeList[i]); - } - return array; -}; - -// ┌────────────────────┐ -// │ Array Manipulation │ -// └────────────────────┘ - -calcite.arr = {}; - -// return the index of an object in an array with optional offset -calcite.arr.indexOf = function (obj, arr, offset) { - var i = offset || 0; - - if (arr.indexOf) { - return arr.indexOf(obj, i); - } - - for (i; i < arr.length; i++) { - if (arr[i] === obj) { - return i; + // stop and event from bubbling up the DOM tree + function stopPropagation (e) { + e = e || window.event; + if (e.stopPropagation) { + return e.stopPropagation(); + } + if (e.cancelBubble) { + e.cancelBubble = true; } } - return -1; -}; - -// ┌───────────────────────────┐ -// │ Browser Feature Detection │ -// └───────────────────────────┘ -// detect features like touch, ie, etc. - -calcite.browser = {}; - -// detect touch, could be improved for more coverage -calcite.browser.isTouch = function () { - if (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0)) { - return true; - } - return false; -}; - -// ┌─────────────┐ -// │ JS Patterns │ -// └─────────────┘ -// javascript logic for ui patterns - -function findElements (className) { - var elements = document.querySelectorAll(className); - if (elements.length) { - return calcite.dom.nodeListToArray(elements); - } else { - return false; - } -} + // ┌────────────────────┐ + // │ Class Manipulation │ + // └────────────────────┘ -// remove 'is-active' class from every element in an array -function removeActive (array) { - if (typeof array == 'object') { - array = calcite.dom.nodeListToArray(array); + // check if an element has a specific class + function hasClass (domNode, className) { + var elementClass = ' ' + domNode.className + ' '; + return elementClass.indexOf(' ' + className + ' ') !== -1; } - array.forEach(function (item) { - calcite.dom.removeClass(item, 'is-active'); - }); -} - -// remove 'is-active' from array, add to element -function toggleActive (array, el) { - var isActive = calcite.dom.hasClass(el, 'is-active'); - if (isActive) { - calcite.dom.removeClass(el, 'is-active'); - } else { - removeActive(array); - calcite.dom.addClass(el, 'is-active'); - } -} - -// ┌───────────┐ -// │ Accordion │ -// └───────────┘ -// collapsible accordion list -calcite.accordion = function () { - var accordions = findElements('.js-accordion'); - - if (!accordions) { - return; + // add one or more classes to an element + function addClass (domNode, classes) { + classes.split(' ').forEach(function (c) { + if (!hasClass(domNode, c)) { + domNode.className += ' ' + c; + } + }); } - for (var i = 0; i < accordions.length; i++) { - var children = accordions[i].children; - for (var j = 0; j < children.length; j++) { - calcite.dom.addEvent(children[j], calcite.dom.event(), toggleAccordion); - } + // remove one or more classes from an element + function removeClass (domNode, classes) { + var elementClass = ' ' + domNode.className + ' '; + classes.split(' ').forEach(function (c) { + elementClass = elementClass.replace(' ' + c + ' ', ' '); + }); + domNode.className = elementClass.trim(); } - function toggleAccordion (event) { - var parent = calcite.dom.closest('accordion-section', calcite.dom.eventTarget(event)); - if (calcite.dom.hasClass(parent, 'is-active')) { - calcite.dom.removeClass(parent, 'is-active'); + // if domNode has the class, remove it, else add it + function toggleClass (domNode, className) { + if (hasClass(domNode, className)) { + removeClass(domNode, className); } else { - calcite.dom.addClass(parent, 'is-active'); + addClass(domNode, className); } } -}; + // ┌─────┐ + // │ DOM │ + // └─────┘ -// ┌──────────┐ -// │ Carousel │ -// └──────────┘ -// show carousel with any number of slides - -calcite.carousel = function () { - - var carousels = findElements('.js-carousel'); - - if (!carousels) { - return; + // returns closest element up the DOM tree matching a given class + function closest (className, context) { + var result, current; + for (current = context; current; current = current.parentNode) { + if (current.nodeType === 1 && hasClass(current, className)) { + result = current; + break; + } + } + return current; } - for (var i = 0; i < carousels.length; i++) { - - var carousel = carousels[i]; - var wrapper = carousel.querySelectorAll('.carousel-slides')[0]; - var slides = carousel.querySelectorAll('.carousel-slide'); - var toggles = calcite.dom.nodeListToArray(carousel.querySelectorAll('.js-carousel-link')); - - wrapper.style.width = slides.length * 100 + '%'; - - calcite.dom.addClass(slides[0], 'is-active'); - calcite.dom.addClass(carousel, 'is-first-slide'); - - for (var k = 0; k < slides.length; k++) { - slides[k].style.width = 100 / slides.length + '%'; + // get an attribute for an element + function getAttr (domNode, attr) { + if (domNode.getAttribute) { + return domNode.getAttribute(attr); } - for (var j = 0; j < toggles.length; j++) { - calcite.dom.addEvent(toggles[j], calcite.dom.event(), toggleSlide); + var result; + var attrs = domNode.attributes; + + for (var i = 0; i < attrs.length; i++) { + if (attrs[i].nodeName === attr) { + result = attrs[i].nodeValue; + } } + return result; } - function toggleSlide (e) { - calcite.dom.preventDefault(e); - var link = calcite.dom.eventTarget(e); - var index = calcite.dom.getAttr(link, 'data-slide'); - var carousel = calcite.dom.closest('carousel', link); - var current = carousel.querySelectorAll('.carousel-slide.is-active')[0]; - var slides = carousel.querySelectorAll('.carousel-slide'); - var wrapper = carousel.querySelectorAll('.carousel-slides')[0]; - - if (index == 'prev') { - index = calcite.arr.indexOf(current, slides); - if (index === 0) { index = 1; } - } else if (index == 'next') { - index = calcite.arr.indexOf(current, slides) + 2; - if (index > slides.length) { index = slides.length; } + // turn a domNodeList into an array + function nodeListToArray (domNodeList) { + var array = []; + for (var i = 0; i < domNodeList.length; i++) { + array.push(domNodeList[i]); } - - calcite.dom.removeClass(carousel, 'is-first-slide is-last-slide'); - - if (index == slides.length) { calcite.dom.addClass(carousel, 'is-last-slide');} - if (index == 1) { calcite.dom.addClass(carousel, 'is-first-slide');} - - removeActive(slides); - calcite.dom.addClass(slides[index - 1], 'is-active'); - var offset = (index - 1)/slides.length * -100 + '%'; - wrapper.style.transform= 'translate3d(' + offset + ',0,0)'; + return array; } -}; - -// ┌──────────┐ -// │ Dropdown │ -// └──────────┘ -// show and hide dropdown menus + // ┌─────────────┐ + // │ JS Patterns │ + // └─────────────┘ + // helper functions for ui patterns -calcite.dropdown = function () { - var toggles = findElements('.js-dropdown-toggle'); - var dropdowns = findElements('.js-dropdown'); - if (!dropdowns) { - return; + // return an array of elements matching a query + function findElements (query) { + var elements = document.querySelectorAll(query); + return nodeListToArray(elements); } - function closeAllDropdowns () { - for (var i = 0; i < dropdowns.length; i++) { - calcite.dom.removeClass(dropdowns[i], 'is-active'); + // remove 'is-active' class from every element in an array + function removeActive (array) { + if (typeof array == 'object') { + array = nodeListToArray(array); } + array.forEach(function (item) { + removeClass(item, 'is-active'); + }); } - function toggleDropdown (dropdown) { - var isActive = calcite.dom.hasClass(dropdown, 'is-active'); + // remove 'is-active' from array, add to element + function toggleActive (array, el) { + var isActive = hasClass(el, 'is-active'); if (isActive) { - calcite.dom.removeClass(dropdown, 'is-active'); + removeClass(el, 'is-active'); } else { - closeAllDropdowns(); - calcite.dom.addClass(dropdown, 'is-active'); - calcite.dom.addEvent(document.body, calcite.dom.event(), function(event) { - closeAllDropdowns(); - }); + removeActive(array); + addClass(el, 'is-active'); } } - function bindDropdown (toggle) { - calcite.dom.addEvent(toggle, calcite.dom.event(), function(event) { - calcite.dom.preventDefault(event); - calcite.dom.stopPropagation(event); - var dropdown = calcite.dom.closest('js-dropdown', toggle); - toggleDropdown(dropdown); + // ┌───────────┐ + // │ Accordion │ + // └───────────┘ + // collapsible accordion list + calcite.patterns.accordion = function () { + findElements('.js-accordion').forEach(function (accordion) { + nodeListToArray(accordion.children).forEach(function (child) { + addEvent(child, click(), toggleAccordion); + }); }); - } - for (var i = 0; i < toggles.length; i++) { - bindDropdown(toggles[i]); - } -}; - -// ┌────────┐ -// │ Drawer │ -// └────────┘ -// show and hide drawers -calcite.drawer = function () { + function toggleAccordion (e) { + var parent = closest('accordion-section', eventTarget(e)); + toggleClass(parent, 'is-active'); + } + }; - var toggles = findElements('.js-drawer-toggle'); - var drawers = findElements('.js-drawer'); + // ┌──────────┐ + // │ Dropdown │ + // └──────────┘ + // show and hide dropdown menus + calcite.patterns.dropdown = function () { + var toggles = findElements('.js-dropdown-toggle'); + var dropdowns = findElements('.js-dropdown'); + + function closeAllDropdowns () { + removeEvent(document.body, click(), closeAllDropdowns); + dropdowns.forEach(function (dropdown) { + removeClass(dropdown, 'is-active'); + }); + } - if (!drawers) { - return; - } + function bindToggle (toggle) { + addEvent(toggle, click(), function (e) { + preventDefault(e); + stopPropagation(e); + var dropdown = closest('js-dropdown', toggle); + closeAllDropdowns(); + addClass(dropdown, 'is-active'); + addEvent(document.body, click(), closeAllDropdowns); + }); + } - function bindToggle (toggle) { - calcite.dom.addEvent(toggle, calcite.dom.event(), function(event) { - calcite.dom.preventDefault(event); - var target = calcite.dom.getAttr(toggle, 'data-drawer'); - for (var i = 0; i < drawers.length; i++) { - var drawer = drawers[i]; - var isTarget = calcite.dom.getAttr(drawers[i], 'data-drawer'); - if (target == isTarget) { - toggleActive(drawers, drawer); - } - } - }); - } + toggles.forEach(bindToggle); + }; - function bindDrawer (drawer) { - calcite.dom.addEvent(drawer, calcite.dom.event(), function(event) { - if (calcite.dom.hasClass(event.target, 'drawer')) { + // ┌────────┐ + // │ Drawer │ + // └────────┘ + // show and hide drawers + calcite.patterns.drawer = function () { + var toggles = findElements('.js-drawer-toggle'); + var drawers = findElements('.js-drawer'); + + toggles.forEach(function (toggle) { + addEvent(toggle, click(), function (e) { + preventDefault(e); + var drawerId = getAttr(toggle, 'data-drawer'); + var drawer = document.querySelector('.js-drawer[data-drawer="' + drawerId + '"'); toggleActive(drawers, drawer); - } + }); }); - } - for (var i = 0; i < toggles.length; i++) { - bindToggle(toggles[i]); - } - for (var j = 0; j < drawers.length; j++) { - bindDrawer(drawers[j]); - } -}; - -// ┌───────────────┐ -// │ Expanding Nav │ -// └───────────────┘ -// show and hide exanding nav located under topnav -calcite.expandingNav = function () { - var toggles = findElements('.js-expanding-toggle'); - var expanders = findElements('.js-expanding'); - - if (!expanders) { - return; - } - - function bindToggle (toggle) { - calcite.dom.addEvent(toggle, calcite.dom.event(), function(event) { - calcite.dom.preventDefault(event); - - var sectionName = calcite.dom.getAttr(toggle, 'data-expanding-nav'); - var sections = document.querySelectorAll('.js-expanding-nav'); - var section = document.querySelectorAll('.js-expanding-nav[data-expanding-nav="' + sectionName + '"]')[0]; - var expander = calcite.dom.closest('js-expanding', section); - var isOpen = calcite.dom.hasClass(expander, 'is-active'); - var shouldClose = calcite.dom.hasClass(section, 'is-active'); - - if (isOpen) { - if (shouldClose) { - calcite.dom.removeClass(expander, 'is-active'); + drawers.forEach(function (drawer) { + addEvent(drawer, click(), function (e) { + if (hasClass(eventTarget(e), 'drawer')) { + toggleActive(drawers, drawer); } - toggleActive(sections, section); - } else { - toggleActive(sections, section); - calcite.dom.addClass(expander, 'is-active'); - } - + }); }); - } - - for (var i = 0; i < toggles.length; i++) { - bindToggle(toggles[i]); - } -}; - -// ┌───────┐ -// │ Modal │ -// └───────┘ -// show and hide modal dialogues - -calcite.modal = function () { + }; - var toggles = findElements('.js-modal-toggle'); - var modals = findElements('.js-modal'); + // ┌───────────────┐ + // │ Expanding Nav │ + // └───────────────┘ + // show and hide exanding nav located under topnav + calcite.patterns.expandingNav = function () { + var toggles = findElements('.js-expanding-toggle'); + var expanders = findElements('.js-expanding'); + var sections = document.querySelectorAll('.js-expanding-nav'); + + toggles.forEach(function (toggle) { + addEvent(toggle, click(), function (e) { + preventDefault(e); + + var sectionId = getAttr(toggle, 'data-expanding-nav'); + var section = document.querySelector('.js-expanding-nav[data-expanding-nav="' + sectionId + '"]'); + var expander = closest('js-expanding', section); + var isOpen = hasClass(expander, 'is-active'); + var shouldClose = hasClass(section, 'is-active'); - if (!modals) { - return; - } + toggleActive(sections, section); - function bindToggle (toggle) { - calcite.dom.addEvent(toggle, calcite.dom.event(), function(event) { - calcite.dom.preventDefault(event); - var target = calcite.dom.getAttr(toggle, 'data-modal'); - for (var i = 0; i < modals.length; i++) { - var modal = modals[i]; - var isTarget = calcite.dom.getAttr(modals[i], 'data-modal'); - if (target == isTarget) { - toggleActive(modals, modal); + if (isOpen && shouldClose) { + removeClass(expander, 'is-active'); + } else { + addClass(expander, 'is-active'); } - } + }); }); - } + }; - function bindModal (modal) { - calcite.dom.addEvent(modal, calcite.dom.event(), function(event) { - calcite.dom.preventDefault(event); - toggleActive(modals, modal); + // ┌───────┐ + // │ Modal │ + // └───────┘ + // show and hide modal dialogues + calcite.patterns.modal = function () { + var toggles = findElements('.js-modal-toggle'); + var modals = findElements('.js-modal'); + + toggles.forEach(function (toggle) { + addEvent(toggle, click(), function (e) { + preventDefault(e); + var modal; + var modalId = getAttr(toggle, 'data-modal'); + if (modalId) { + modal = document.querySelector('.js-modal[data-modal="' + modalId + '"'); + } else { + modal = closest('js-modal', toggle); + } + toggleActive(modals, modal); + }); }); - } - - for (var i = 0; i < toggles.length; i++) { - bindToggle(toggles[i]); - } - for (var j = 0; j < modals.length; j++) { - bindModal(modals[j]); - } -}; - - -// ┌──────┐ -// │ Tabs │ -// └──────┘ -// tabbed content pane -calcite.tabs = function () { - var tabs = findElements('.js-tab'); - var tabGroups = findElements('.js-tab-group'); - - if (!tabs) { - return; - } - - // set max width for each tab - for (var j = 0; j < tabGroups.length; j++) { - var tabsInGroup = tabGroups[j].querySelectorAll('.js-tab'); - var percent = 100 / tabsInGroup.length; - for (var k = 0; k < tabsInGroup.length; k++){ - tabsInGroup[k].style.maxWidth = percent + '%'; - } - } - - function switchTab (event) { - calcite.dom.preventDefault(event); - - var tab = calcite.dom.closest('js-tab', calcite.dom.eventTarget(event)); - var tabGroup = calcite.dom.closest('js-tab-group', tab); - var tabs = tabGroup.querySelectorAll('.js-tab'); - var contents = tabGroup.querySelectorAll('.js-tab-section'); - var index = calcite.arr.indexOf(tab, tabs); - - removeActive(tabs); - removeActive(contents); - - calcite.dom.addClass(tab, 'is-active'); - calcite.dom.addClass(contents[index], 'is-active'); - } - - // attach the switchTab event to all tabs - for (var i = 0; i < tabs.length; i++) { - calcite.dom.addEvent(tabs[i], calcite.dom.event(), switchTab); - } - -}; - -// ┌────────┐ -// │ Sticky │ -// └────────┘ -// sticks things to the window + modals.forEach(function (modal) { + addEvent(modal, click(), function (e) { + stopPropagation(e); + if (eventTarget(e) === modal) { + toggleActive(modals, modal); + } + }); + }); + }; -calcite.sticky = function () { - var elements = findElements('.js-sticky'); + // ┌──────┐ + // │ Tabs │ + // └──────┘ + // tabbed content pane + calcite.patterns.tabs = function () { + var tabs = findElements('.js-tab'); + var tabGroups = findElements('.js-tab-group'); + + // set max width for each tab + tabGroups.forEach(function (tab) { + var tabsInGroup = tab.querySelectorAll('.js-tab'); + var percent = 100 / tabsInGroup.length; + for (var i = 0; i < tabsInGroup.length; i++) { + tabsInGroup[i].style.maxWidth = percent + '%'; + } + }); - if (!elements) { - return; - } + function switchTab (e) { + preventDefault(e); - var stickies = []; + var tab = closest('js-tab', eventTarget(e)); + var tabGroup = closest('js-tab-group', tab); + var tabs = tabGroup.querySelectorAll('.js-tab'); + var contents = tabGroup.querySelectorAll('.js-tab-section'); + var index = nodeListToArray(tabs).indexOf(tab); - for (var i = 0; i < elements.length; i++) { - var el = elements[i]; - var top = el.offsetTop; - var dataTop = calcite.dom.getAttr(el, 'data-top'); + removeActive(tabs); + removeActive(contents); - if (dataTop) { - top = top - parseInt(dataTop, 0); + addClass(tab, 'is-active'); + addClass(contents[index], 'is-active'); } - stickies.push({ - active: false, - top: top, - shim: el.cloneNode('deep'), - element: el + tabs.forEach(function (tab) { + addEvent(tab, click(), switchTab); }); - } + }; - function handleScroll(item, offset) { - var elem = item.element; - var parent = elem.parentNode; - var distance = item.top - offset; - var dataTop = calcite.dom.getAttr(el, 'data-top'); - - if (distance < 1 && !item.active) { - item.shim.style.visiblity = 'hidden'; - parent.insertBefore(item.shim, elem); - calcite.dom.addClass(elem, 'is-sticky'); - item.active = true; - elem.style.top = dataTop + 'px'; - } else if (item.active && offset < item.top){ - parent.removeChild(item.shim); - calcite.dom.removeClass(elem, 'is-sticky'); - elem.style.top = null; - item.active = false; - } - } + // ┌────────┐ + // │ Sticky │ + // └────────┘ + // sticks things to the window + calcite.patterns.sticky = function () { + var elements = findElements('.js-sticky'); + var stickies = elements.map(function (el) { + var offset = el.offsetTop; + var dataTop = getAttr(el, 'data-top') || 0; + return { + active: false, + top: offset - parseInt(dataTop, 0), + shim: el.cloneNode('deep'), + element: el + }; + }); - calcite.dom.addEvent(window, 'scroll', function() { - var offset = window.pageYOffset; - for (var i = 0; i < stickies.length; i++) { - handleScroll(stickies[i], offset); + function handleScroll(item, offset) { + var el = item.element; + var parent = el.parentNode; + var distance = item.top - offset; + var dataTop = getAttr(el, 'data-top'); + + if (distance < 1 && !item.active) { + item.shim.style.visiblity = 'hidden'; + parent.insertBefore(item.shim, el); + addClass(el, 'is-sticky'); + item.active = true; + el.style.top = dataTop + 'px'; + } else if (item.active && offset < item.top) { + parent.removeChild(item.shim); + removeClass(el, 'is-sticky'); + el.style.top = null; + item.active = false; + } } - }); -}; + addEvent(window, 'scroll', function () { + var offset = window.pageYOffset; + stickies.forEach(function (sticky) { + handleScroll(sticky, offset); + }); + }); -// ┌────────────────────┐ -// │ Initialize Calcite │ -// └────────────────────┘ -// start up Calcite and attach all the patterns -// optionally pass an array of patterns you'd like to watch + }; -calcite.init = function (patterns) { - if (patterns) { - for (var i = 0; i < patterns.length; i++) { - calcite[patterns[i]](); - } - } else { - calcite.modal(); - calcite.dropdown(); - calcite.drawer(); - calcite.expandingNav(); - calcite.tabs(); - calcite.accordion(); - calcite.carousel(); - calcite.sticky(); + // ┌────────────────────┐ + // │ Initialize Calcite │ + // └────────────────────┘ + // start up Calcite and attach all the patterns + // optionally pass an array of patterns you'd like to watch + function init (patterns) { + patterns = patterns || Object.keys(calcite.patterns); + patterns.forEach(function (pattern) { + calcite.patterns[pattern](); + }); } - // add a touch class to the body - if ( calcite.browser.isTouch() ) { - calcite.dom.addClass(document.body, 'calcite-touch'); + // ┌────────────────┐ + // │ Expose Calcite │ + // └────────────────┘ + // make calcite available to amd, common-js, or globally + if (typeof define === 'function' && define.amd) { + define(function () { return calcite; }); + } else if (typeof exports === 'object') { + module.exports = calcite; + } else { + // if something called calcite already exists, + // save it for recovery via calcite.noConflict() + var oldCalcite = window.calcite; + calcite.noConflict = function () { + window.calcite = oldCalcite; + return this; + }; + window.calcite = calcite; } -}; - -// ┌───────────────────┐ -// │ Expose Calcite.js │ -// └───────────────────┘ -// implementation borrowed from Leaflet - -// define calcite as a global variable, saving the original to restore later if needed -function expose () { - var oldCalcite = window.calcite; - - calcite.noConflict = function () { - window.calcite = oldCalcite; - return this; - }; - - window.calcite = calcite; -} - -// no NPM/AMD for now because it just causes issues -// @TODO: bust them into AMD & NPM distros - -// // define Calcite for CommonJS module pattern loaders (NPM, Browserify) -// if (typeof module === 'object' && typeof module.exports === 'object') { -// module.exports = calcite; -// } - -// // define Calcite as an AMD module -// else if (typeof define === 'function' && define.amd) { -// define(calcite); -// } - -expose(); })(); diff --git a/lib/sass/calcite-web.scss b/lib/sass/calcite-web.scss index ac25cd297..b58d8e59d 100644 --- a/lib/sass/calcite-web.scss +++ b/lib/sass/calcite-web.scss @@ -113,7 +113,6 @@ $include-sticky: true !default; @import "calcite-web/patterns/tabs"; @import "calcite-web/patterns/modal"; @import "calcite-web/patterns/accordion"; -@import "calcite-web/patterns/carousel"; @import "calcite-web/patterns/drawers"; // Modifiers diff --git a/lib/sass/calcite-web/patterns/_carousel.scss b/lib/sass/calcite-web/patterns/_carousel.scss deleted file mode 100644 index 50f52f0ab..000000000 --- a/lib/sass/calcite-web/patterns/_carousel.scss +++ /dev/null @@ -1,56 +0,0 @@ -// ┌──────────┐ -// │ Carousel │ -// └──────────┘ -// ↳ http://esri.github.io/calcite-web/patterns/#carousel -// ↳ patterns → _carousel.md - -@mixin carousel() { - overflow-x: hidden; - position: relative; -} - - @mixin carousel-slides() { - @include transform(translate3d(0,0,0)); - @include transition-prefixed(transform $transition); - } - - @mixin carousel-slide() { - position: relative; - float: left; - height: 100%; - } - - // Next and Prev Buttons - %carousel-button { - position: absolute; - top: 50%; - margin-top: -$baseline/2; - width: $baseline; - height: $baseline; - text-align: center; - color: $white; - background: $transparent-black; - z-index: 10; - } - - @mixin carousel-prev() { - @extend %carousel-button; - left: 0; - } - - @mixin carousel-next() { - @extend %carousel-button; - right: 0; - } - -@if $include-carousel == true { -.carousel { @include carousel()} - .carousel-slides { @include carousel-slides()} - .carousel-slide { @include carousel-slide()} - .carousel-prev { @include carousel-prev()} - .carousel-next { @include carousel-next()} - .is-first-slide.carousel-prev, .is-last-slide.carousel-next { - pointer-events: none; - opacity: 0.2; - } -} diff --git a/package.json b/package.json index 24a77a356..3d585c395 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "calcite-web", - "version": "0.2.3", + "version": "0.3.0", "description": "SASS & CSS Framework for Esri websites", "private": true, "homepage": "https://github.com/esri/calcite-web",