3 exports.__esModule = true;
4 exports.$$ = exports.$ = undefined;
6 var _templateObject = _taggedTemplateLiteralLoose(['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.'], ['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.']);
8 exports.isReal = isReal;
10 exports.getEl = getEl;
11 exports.createEl = createEl;
12 exports.textContent = textContent;
13 exports.insertElFirst = insertElFirst;
14 exports.getElData = getElData;
15 exports.hasElData = hasElData;
16 exports.removeElData = removeElData;
17 exports.hasElClass = hasElClass;
18 exports.addElClass = addElClass;
19 exports.removeElClass = removeElClass;
20 exports.toggleElClass = toggleElClass;
21 exports.setElAttributes = setElAttributes;
22 exports.getElAttributes = getElAttributes;
23 exports.getAttribute = getAttribute;
24 exports.setAttribute = setAttribute;
25 exports.removeAttribute = removeAttribute;
26 exports.blockTextSelection = blockTextSelection;
27 exports.unblockTextSelection = unblockTextSelection;
28 exports.findElPosition = findElPosition;
29 exports.getPointerPosition = getPointerPosition;
30 exports.isTextNode = isTextNode;
31 exports.emptyEl = emptyEl;
32 exports.normalizeContent = normalizeContent;
33 exports.appendContent = appendContent;
34 exports.insertContent = insertContent;
36 var _document = require('global/document');
38 var _document2 = _interopRequireDefault(_document);
40 var _window = require('global/window');
42 var _window2 = _interopRequireDefault(_window);
44 var _guid = require('./guid.js');
46 var Guid = _interopRequireWildcard(_guid);
48 var _log = require('./log.js');
50 var _log2 = _interopRequireDefault(_log);
52 var _tsml = require('tsml');
54 var _tsml2 = _interopRequireDefault(_tsml);
56 var _obj = require('./obj');
58 function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
60 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
62 function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; } /**
69 * Detect if a value is a string with any non-whitespace characters.
75 * - True if the string is non-blank
79 function isNonBlankString(str) {
80 return typeof str === 'string' && /\S/.test(str);
84 * Throws an error if the passed string has whitespace. This is used by
85 * class methods to be relatively consistent with the classList API.
88 * The string to check for whitespace.
91 * Throws an error if there is whitespace in the string.
94 function throwIfWhitespace(str) {
96 throw new Error('class has illegal whitespace characters');
101 * Produce a regular expression for matching a className within an elements className.
103 * @param {string} className
104 * The className to generate the RegExp for.
107 * The RegExp that will check for a specific `className` in an elements
110 function classRegExp(className) {
111 return new RegExp('(^|\\s)' + className + '($|\\s)');
115 * Whether the current DOM interface appears to be real.
122 // Both document and window will never be undefined thanks to `global`.
123 _document2['default'] === _window2['default'].document &&
125 // In IE < 9, DOM methods return "object" as their type, so all we can
126 // confidently check is that it exists.
127 typeof _document2['default'].createElement !== 'undefined'
132 * Determines, via duck typing, whether or not a value is a DOM element.
134 * @param {Mixed} value
138 * - True if it is a DOM element
141 function isEl(value) {
142 return (0, _obj.isObject)(value) && value.nodeType === 1;
146 * Creates functions to query the DOM using a given method.
148 * @param {string} method
149 * The method to create the query with.
154 function createQuerier(method) {
155 return function (selector, context) {
156 if (!isNonBlankString(selector)) {
157 return _document2['default'][method](null);
159 if (isNonBlankString(context)) {
160 context = _document2['default'].querySelector(context);
163 var ctx = isEl(context) ? context : _document2['default'];
165 return ctx[method] && ctx[method](selector);
170 * Shorthand for document.getElementById()
171 * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
174 * The id of the element to get
176 * @return {Element|null}
177 * Element with supplied ID or null if there wasn't one.
180 if (id.indexOf('#') === 0) {
184 return _document2['default'].getElementById(id);
188 * Creates an element and applies properties.
190 * @param {string} [tagName='div']
191 * Name of tag to be created.
193 * @param {Object} [properties={}]
194 * Element properties to be applied.
196 * @param {Object} [attributes={}]
197 * Element attributes to be applied.
199 * @param {String|Element|TextNode|Array|Function} [content]
200 * Contents for the element (see: {@link dom:normalizeContent})
203 * The element that was created.
205 function createEl() {
206 var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';
207 var properties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
208 var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
209 var content = arguments[3];
211 var el = _document2['default'].createElement(tagName);
213 Object.getOwnPropertyNames(properties).forEach(function (propName) {
214 var val = properties[propName];
217 // We originally were accepting both properties and attributes in the
218 // same object, but that doesn't work so well.
219 if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
220 _log2['default'].warn((0, _tsml2['default'])(_templateObject, propName, val));
221 el.setAttribute(propName, val);
223 // Handle textContent since it's not supported everywhere and we have a
225 } else if (propName === 'textContent') {
226 textContent(el, val);
232 Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
233 el.setAttribute(attrName, attributes[attrName]);
237 appendContent(el, content);
244 * Injects text into an element, replacing any existing contents entirely.
246 * @param {Element} el
247 * The element to add text content into
249 * @param {string} text
250 * The text content to add.
253 * The element with added text content.
255 function textContent(el, text) {
256 if (typeof el.textContent === 'undefined') {
259 el.textContent = text;
265 * Insert an element as the first child node of another
267 * @param {Element} child
270 * @param {Element} parent
271 * Element to insert child into
274 function insertElFirst(child, parent) {
275 if (parent.firstChild) {
276 parent.insertBefore(child, parent.firstChild);
278 parent.appendChild(child);
283 * Element Data Store. Allows for binding data to an element without putting it directly on the element.
284 * Ex. Event listeners are stored here.
285 * (also from jsninja.com, slightly modified and updated for closure compiler)
293 * Unique attribute name to store an element's guid in
299 var elIdAttr = 'vdata' + new Date().getTime();
302 * Returns the cache object where data for an element is stored
304 * @param {Element} el
305 * Element to store data for.
308 * The cache object for that el that was passed in.
310 function getElData(el) {
311 var id = el[elIdAttr];
314 id = el[elIdAttr] = Guid.newGUID();
325 * Returns whether or not an element has cached data
327 * @param {Element} el
328 * Check if this element has cached data.
331 * - True if the DOM element has cached data.
334 function hasElData(el) {
335 var id = el[elIdAttr];
341 return !!Object.getOwnPropertyNames(elData[id]).length;
345 * Delete data for the element from the cache and the guid attr from getElementById
347 * @param {Element} el
348 * Remove cached data for this element.
350 function removeElData(el) {
351 var id = el[elIdAttr];
357 // Remove all stored data
360 // Remove the elIdAttr property from the DOM node
364 if (el.removeAttribute) {
365 el.removeAttribute(elIdAttr);
367 // IE doesn't appear to support removeAttribute on the document element
374 * Check if an element has a CSS class
376 * @param {Element} element
379 * @param {string} classToCheck
380 * Class name to check for
383 * - True if the element had the class
387 * Throws an error if `classToCheck` has white space.
389 function hasElClass(element, classToCheck) {
390 throwIfWhitespace(classToCheck);
391 if (element.classList) {
392 return element.classList.contains(classToCheck);
394 return classRegExp(classToCheck).test(element.className);
398 * Add a CSS class name to an element
400 * @param {Element} element
401 * Element to add class name to.
403 * @param {string} classToAdd
407 * The dom element with the added class name.
409 function addElClass(element, classToAdd) {
410 if (element.classList) {
411 element.classList.add(classToAdd);
413 // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
414 // in the case of classList not being supported.
415 } else if (!hasElClass(element, classToAdd)) {
416 element.className = (element.className + ' ' + classToAdd).trim();
423 * Remove a CSS class name from an element
425 * @param {Element} element
426 * Element to remove a class name from.
428 * @param {string} classToRemove
429 * Class name to remove
432 * The dom element with class name removed.
434 function removeElClass(element, classToRemove) {
435 if (element.classList) {
436 element.classList.remove(classToRemove);
438 throwIfWhitespace(classToRemove);
439 element.className = element.className.split(/\s+/).filter(function (c) {
440 return c !== classToRemove;
448 * The callback definition for toggleElClass.
450 * @callback Dom~PredicateCallback
451 * @param {Element} element
452 * The DOM element of the Component.
454 * @param {string} classToToggle
455 * The `className` that wants to be toggled
457 * @return {boolean|undefined}
458 * - If true the `classToToggle` will get added to `element`.
459 * - If false the `classToToggle` will get removed from `element`.
460 * - If undefined this callback will be ignored
464 * Adds or removes a CSS class name on an element depending on an optional
465 * condition or the presence/absence of the class name.
467 * @param {Element} element
468 * The element to toggle a class name on.
470 * @param {string} classToToggle
471 * The class that should be toggled
473 * @param {boolean|PredicateCallback} [predicate]
474 * See the return value for {@link Dom~PredicateCallback}
477 * The element with a class that has been toggled.
479 function toggleElClass(element, classToToggle, predicate) {
481 // This CANNOT use `classList` internally because IE does not support the
482 // second parameter to the `classList.toggle()` method! Which is fine because
483 // `classList` will be used by the add/remove functions.
484 var has = hasElClass(element, classToToggle);
486 if (typeof predicate === 'function') {
487 predicate = predicate(element, classToToggle);
490 if (typeof predicate !== 'boolean') {
494 // If the necessary class operation matches the current state of the
495 // element, no action is required.
496 if (predicate === has) {
501 addElClass(element, classToToggle);
503 removeElClass(element, classToToggle);
510 * Apply attributes to an HTML element.
512 * @param {Element} el
513 * Element to add attributes to.
515 * @param {Object} [attributes]
516 * Attributes to be applied.
518 function setElAttributes(el, attributes) {
519 Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
520 var attrValue = attributes[attrName];
522 if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
523 el.removeAttribute(attrName);
525 el.setAttribute(attrName, attrValue === true ? '' : attrValue);
531 * Get an element's attribute values, as defined on the HTML tag
532 * Attributes are not the same as properties. They're defined on the tag
533 * or with setAttribute (which shouldn't be used with HTML)
534 * This will return true or false for boolean attributes.
536 * @param {Element} tag
537 * Element from which to get tag attributes.
540 * All attributes of the element.
542 function getElAttributes(tag) {
545 // known boolean attributes
546 // we can check for matching boolean properties, but older browsers
547 // won't know about HTML5 boolean attributes that we still read from
548 var knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ',';
550 if (tag && tag.attributes && tag.attributes.length > 0) {
551 var attrs = tag.attributes;
553 for (var i = attrs.length - 1; i >= 0; i--) {
554 var attrName = attrs[i].name;
555 var attrVal = attrs[i].value;
557 // check for known booleans
558 // the matching element property will return a value for typeof
559 if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
560 // the value of an included boolean attribute is typically an empty
561 // string ('') which would equal false if we just check for a false value.
562 // we also don't want support bad code like autoplay='false'
563 attrVal = attrVal !== null ? true : false;
566 obj[attrName] = attrVal;
574 * Get the value of an element's attribute
576 * @param {Element} el
579 * @param {string} attribute
580 * Attribute to get the value of
583 * value of the attribute
585 function getAttribute(el, attribute) {
586 return el.getAttribute(attribute);
590 * Set the value of an element's attribute
592 * @param {Element} el
595 * @param {string} attribute
598 * @param {string} value
599 * Value to set the attribute to
601 function setAttribute(el, attribute, value) {
602 el.setAttribute(attribute, value);
606 * Remove an element's attribute
608 * @param {Element} el
611 * @param {string} attribute
612 * Attribute to remove
614 function removeAttribute(el, attribute) {
615 el.removeAttribute(attribute);
619 * Attempt to block the ability to select text while dragging controls
621 function blockTextSelection() {
622 _document2['default'].body.focus();
623 _document2['default'].onselectstart = function () {
629 * Turn off text selection blocking
631 function unblockTextSelection() {
632 _document2['default'].onselectstart = function () {
638 * The postion of a DOM element on the page.
640 * @typedef {Object} Dom~Position
642 * @property {number} left
645 * @property {number} top
651 * getBoundingClientRect technique from
654 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
656 * @param {Element} el
657 * Element from which to get offset
659 * @return {Dom~Position}
660 * The position of the element that was passed in.
662 function findElPosition(el) {
665 if (el.getBoundingClientRect && el.parentNode) {
666 box = el.getBoundingClientRect();
676 var docEl = _document2['default'].documentElement;
677 var body = _document2['default'].body;
679 var clientLeft = docEl.clientLeft || body.clientLeft || 0;
680 var scrollLeft = _window2['default'].pageXOffset || body.scrollLeft;
681 var left = box.left + scrollLeft - clientLeft;
683 var clientTop = docEl.clientTop || body.clientTop || 0;
684 var scrollTop = _window2['default'].pageYOffset || body.scrollTop;
685 var top = box.top + scrollTop - clientTop;
687 // Android sometimes returns slightly off decimal values, so need to round
689 left: Math.round(left),
695 * x and y coordinates for a dom element or mouse pointer
697 * @typedef {Object} Dom~Coordinates
699 * @property {number} x
700 * x coordinate in pixels
702 * @property {number} y
703 * y coordinate in pixels
707 * Get pointer position in element
708 * Returns an object with x and y coordinates.
709 * The base on the coordinates are the bottom left of the element.
711 * @param {Element} el
712 * Element on which to get the pointer position on
714 * @param {EventTarget~Event} event
717 * @return {Dom~Coordinates}
718 * A Coordinates object corresponding to the mouse position.
721 function getPointerPosition(el, event) {
723 var box = findElPosition(el);
724 var boxW = el.offsetWidth;
725 var boxH = el.offsetHeight;
729 var pageY = event.pageY;
730 var pageX = event.pageX;
732 if (event.changedTouches) {
733 pageX = event.changedTouches[0].pageX;
734 pageY = event.changedTouches[0].pageY;
737 position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
738 position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
744 * Determines, via duck typing, whether or not a value is a text node.
746 * @param {Mixed} value
747 * Check if this value is a text node.
750 * - True if it is a text node
753 function isTextNode(value) {
754 return (0, _obj.isObject)(value) && value.nodeType === 3;
758 * Empties the contents of an element.
760 * @param {Element} el
761 * The element to empty children from
764 * The element with no children
766 function emptyEl(el) {
767 while (el.firstChild) {
768 el.removeChild(el.firstChild);
774 * Normalizes content for eventual insertion into the DOM.
776 * This allows a wide range of content definition methods, but protects
777 * from falling into the trap of simply writing to `innerHTML`, which is
780 * The content for an element can be passed in multiple types and
781 * combinations, whose behavior is as follows:
783 * @param {String|Element|TextNode|Array|Function} content
784 * - String: Normalized into a text node.
785 * - Element/TextNode: Passed through.
786 * - Array: A one-dimensional array of strings, elements, nodes, or functions
787 * (which return single strings, elements, or nodes).
788 * - Function: If the sole argument, is expected to produce a string, element,
789 * node, or array as defined above.
792 * All of the content that was passed in normalized.
794 function normalizeContent(content) {
796 // First, invoke content if it is a function. If it produces an array,
797 // that needs to happen before normalization.
798 if (typeof content === 'function') {
802 // Next up, normalize to an array, so one or many items can be normalized,
803 // filtered, and returned.
804 return (Array.isArray(content) ? content : [content]).map(function (value) {
806 // First, invoke value if it is a function to produce a new value,
807 // which will be subsequently normalized to a Node of some kind.
808 if (typeof value === 'function') {
812 if (isEl(value) || isTextNode(value)) {
816 if (typeof value === 'string' && /\S/.test(value)) {
817 return _document2['default'].createTextNode(value);
819 }).filter(function (value) {
825 * Normalizes and appends content to an element.
827 * @param {Element} el
828 * Element to append normalized content to.
831 * @param {String|Element|TextNode|Array|Function} content
832 * See the `content` argument of {@link dom:normalizeContent}
835 * The element with appended normalized content.
837 function appendContent(el, content) {
838 normalizeContent(content).forEach(function (node) {
839 return el.appendChild(node);
845 * Normalizes and inserts content into an element; this is identical to
846 * `appendContent()`, except it empties the element first.
848 * @param {Element} el
849 * Element to insert normalized content into.
851 * @param {String|Element|TextNode|Array|Function} content
852 * See the `content` argument of {@link dom:normalizeContent}
855 * The element with inserted normalized content.
858 function insertContent(el, content) {
859 return appendContent(emptyEl(el), content);
863 * Finds a single DOM element matching `selector` within the optional
864 * `context` of another DOM element (defaulting to `document`).
866 * @param {string} selector
867 * A valid CSS selector, which will be passed to `querySelector`.
869 * @param {Element|String} [context=document]
870 * A DOM element within which to query. Can also be a selector
871 * string in which case the first matching element will be used
872 * as context. If missing (or no element matches selector), falls
873 * back to `document`.
875 * @return {Element|null}
876 * The element that was found or null.
878 var $ = exports.$ = createQuerier('querySelector');
881 * Finds a all DOM elements matching `selector` within the optional
882 * `context` of another DOM element (defaulting to `document`).
884 * @param {string} selector
885 * A valid CSS selector, which will be passed to `querySelectorAll`.
887 * @param {Element|String} [context=document]
888 * A DOM element within which to query. Can also be a selector
889 * string in which case the first matching element will be used
890 * as context. If missing (or no element matches selector), falls
891 * back to `document`.
894 * A element list of elements that were found. Will be empty if none were found.
897 var $$ = exports.$$ = createQuerier('querySelectorAll');