UNPKG

postcss-speech-bubble

Version:

PostCSS plugin creates speech bubbles with just 1-2 lines of CSS

286 lines (243 loc) 10.2 kB
'use strict'; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var postcss = require('postcss'); var getBeakerDeclarationValues = function getBeakerDeclarationValues(rule) { var bubbleBeakerValues = []; var position = null; var bubbleBackColor = null; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = rule.nodes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var node = _step.value; switch (node.prop.trim().toLowerCase()) { case 'bubble-beaker': bubbleBeakerValues = node.value.split(' '); rule.removeChild(node); break; case 'position': position = node.value; break; case 'bubble-background': bubbleBackColor = node.value; rule.removeChild(node); break; } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return { bubbleBeakerValues: bubbleBeakerValues, position: position, bubbleBackColor: bubbleBackColor }; }; var extractBubbleAndBeakerSpecs = function extractBubbleAndBeakerSpecs(bubbleValues, beakerValues) { var bubble = {}; var beaker = {}; if (bubbleValues && bubbleValues.length !== 4) { throw 'Bubble is not correctly specified. Please check the docs\n and provide the right values.'; } if (beakerValues && beakerValues.length !== 2) { throw 'Bubble beaker is not specified correctly. Please check the docs and\n provide the right values.'; } var _bubbleValues = _slicedToArray(bubbleValues, 4); bubble.borderSize = _bubbleValues[0]; bubble.borderRadius = _bubbleValues[1]; bubble.type = _bubbleValues[2]; bubble.color = _bubbleValues[3]; if (beakerValues[1].indexOf('-') >= 0) { beaker.direction = beakerValues ? beakerValues[1].split('-')[0] : null; beaker.position = beakerValues ? beakerValues[1].split('-')[1] : null; } else { throw 'Beaker is not provided correctly. Please check the docs and\n provide the right values.'; } var possibleDirections = ['top', 'bottom', 'left', 'right']; var possiblePositions = ['top', 'bottom', 'left', 'right', 'center', 'middle']; if (possibleDirections.indexOf(beaker.direction) < 0 || possiblePositions.indexOf(beaker.position) < 0) { throw 'Beaker is not provided correctly. Please specify bubble-beaker:\n <beaker size> <beaker position>. Beaker position should be top-left,\n top-right, bottom-center, left-middle, etc.'; } beaker.size = beakerValues ? beakerValues[0] : null; if (beaker.size.indexOf('px') < 0) { throw 'Expecting a px value for beaker size. Please do not provide other units'; } return { bubble: bubble, beaker: beaker }; }; var addBubble = function addBubble(bubble, beaker, position, rule) { // Add border size for the bubble if (parseInt(bubble.borderSize, 10) > 0) { rule.append('border: ' + bubble.borderSize + ' solid ' + bubble.color); } if (bubble.type.toLowerCase() === 'solid') { rule.append('background-color: ' + bubble.color); } if (bubble.backColor) { rule.append('background-color: ' + bubble.backColor); } // Add border radius if any if (parseInt(bubble.borderRadius, 10) > 0) { rule.append('border-radius: ' + bubble.borderRadius); } // Adjust the positioning of the bubble based on beaker if (beaker.size && beaker.direction && parseInt(beaker.size, 10) > 0) { rule.append('margin-' + beaker.direction + ':' + beaker.size); } // Add position relative to container so the beaker will appear correctly. if (!position) { rule.append('position: relative'); } else if (position !== 'absolute' || position !== 'relative') { throw 'Position for the bubble should either be absolute or relative.'; } }; var addBeaker = function addBeaker(bubble, beaker, rule) { // Add the beaker now var beforeRule = postcss.rule({ selector: rule.selector + ':before' }); var afterRule = null; if (bubble.type === 'hollow') { // We need to add another beaker the same background color as the bubble // to create a caret look instead of triangle. afterRule = postcss.rule({ selector: rule.selector + ':after' }); } var commonRules = ['content: \'\'', 'position: absolute']; var afterColor = bubble.backColor ? bubble.backColor : 'white'; var transparentTriangle = beaker.size + ' solid transparent'; var solidTriangle = beaker.size + ' solid ' + bubble.color; var border = parseInt(bubble.borderSize.split('px')[0], 10) || 1; border = border > 1 ? border + 1 : 1; // First add solid colored beaker switch (beaker.direction.toLowerCase()) { case 'top': beforeRule.append('top: -' + beaker.size); commonRules.push('border-left: ' + transparentTriangle); commonRules.push('border-right: ' + transparentTriangle); beforeRule.append('border-bottom: ' + solidTriangle); if (afterRule) { afterRule.append('top: -' + (parseInt(beaker.size.split('px')[0], 10) - border) + 'px'); afterRule.append('border-bottom: ' + beaker.size + ' solid ' + afterColor); } break; case 'bottom': beforeRule.append('bottom: -' + beaker.size); commonRules.push('border-left: ' + transparentTriangle); commonRules.push('border-right: ' + transparentTriangle); beforeRule.append('border-top: ' + solidTriangle); if (afterRule) { afterRule.append('bottom: -' + (parseInt(beaker.size.split('px')[0], 10) - border) + 'px'); afterRule.append('border-top: ' + beaker.size + ' solid ' + afterColor); } break; case 'left': beforeRule.append('left: -' + beaker.size); commonRules.push('border-top: ' + transparentTriangle); commonRules.push('border-bottom: ' + transparentTriangle); beforeRule.append('border-right: ' + solidTriangle); if (afterRule) { afterRule.append('left: -' + (parseInt(beaker.size.split('px')[0], 10) - border) + 'px'); afterRule.append('border-right: ' + beaker.size + ' solid ' + afterColor); } break; case 'right': beforeRule.append('right: -' + beaker.size); commonRules.push('border-top: ' + transparentTriangle); commonRules.push('border-bottom: ' + transparentTriangle); beforeRule.append('border-left: ' + solidTriangle); if (afterRule) { afterRule.append('right: -' + (parseInt(beaker.size.split('px')[0], 10) - border) + 'px'); afterRule.append('border-left: ' + beaker.size + ' solid ' + afterColor); } break; default: throw 'Provide correct position for bubble-beaker. Some examples\n are top-right, top-center, left-middle, left-top'; } var beakerPos = parseInt(beaker.size.split('px')[0], 10) * 1.5 + 'px'; switch (beaker.position.toLowerCase()) { case 'left': commonRules.push('left: ' + beakerPos); break; case 'right': commonRules.push('right: ' + beakerPos); break; case 'center': commonRules.push('left: calc(50% - ' + beaker.size + ')'); commonRules.push('transformX: -50%'); break; case 'middle': commonRules.push('top: calc(50% - ' + beaker.size + ')'); commonRules.push('transformY: -50%'); break; case 'top': commonRules.push('top: ' + beakerPos); break; case 'bottom': commonRules.push('bottom: ' + beakerPos); break; } var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = commonRules[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var _rule = _step2.value; beforeRule.append(_rule); if (afterRule) afterRule.append(_rule); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } rule.parent.nodes.push(beforeRule); if (afterRule) rule.parent.nodes.push(afterRule); }; module.exports = postcss.plugin('postcss-bubble', function (opts) { opts = opts || {}; return function (css) { css.walkDecls(function (decl, i) { var rule = decl.parent; var value = decl.prop; if (decl.prop === 'bubble') { // Get bubble beaker if specified var bubble = {}; var beaker = {}; var obj = getBeakerDeclarationValues(rule); var bubbleBeakerValues = obj.bubbleBeakerValues; var position = obj.position; var bubbleBackColor = obj.bubbleBackColor; // Consolidate all the values to construct var bubbleValues = decl.value.split(' '); var specs = extractBubbleAndBeakerSpecs(bubbleValues, bubbleBeakerValues); bubble = specs.bubble; bubble.backColor = bubbleBackColor; beaker = specs.beaker; addBubble(bubble, beaker, position, rule); addBeaker(bubble, beaker, rule); rule.removeChild(decl); } }); }; });