Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

palettify

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

palettify - npm Package Compare versions

Comparing version 0.0.0 to 1.0.0

src/_settings.scss

717

dist/palettify.common.js
/*!
* palettify v0.0.0
* palettify v1.0.0
* (c) 2017 Dobromir Hristov

@@ -40,137 +40,376 @@ * Released under the MIT License.

/**
* palettify
* @param {Object} opts
* @param {String | NodeList} opts.imageTarget
* @param {String | NodeList} opts.hoverTarget
* @param {String} opts.attachBoxShadowTo
* @param {String | Number} opts.opacity
* @param {String} opts.opacitySecondary
* @param {String} opts.colorIndexToUse
* @param {String} opts.boxShadowTemplate - Provide a boxShadow template to apply. '0 2px 2px {color}, 3px 3px {colorSecondary}'
* @callback ResolverFn
* @param {string} varName - variable name before being parsed.
* For example: {a.b.c} -> 'a.b.c', { x } -> 'x'
* @param {Object} view - the view object that was passed to .render() function
* @returns {string|number|boolean|Object|undefined} the value to be
* interpolated. If the function returns undefined, the value resolution
* algorithm will go ahead with the default behaviour (resolving the
* variable name from the provided object).
*/
function palettify () {
/**
* Extract the colors from image tag or Background-image inline style
* @param {HTMLElement} paletteTarget - The palette target to get the colors form
* @return {Array} Returns an object with RGB colors
* @private
*/
function __extractColors (paletteTarget) {
var image = paletteTarget;
if (!paletteTarget) { throw Error('Target is not an element', paletteTarget) }
// Our sample is not a img tag so we try to get its background image.
if (paletteTarget.tagName !== 'IMG' && paletteTarget.style.backgroundImage) { Error('Tag provided is not an image and does not have a background-image style attached to it.'); }
// Its not an IMG tag so we try to crate one under the hood.
if (paletteTarget.tagName !== 'IMG') {
image = new Image(paletteTarget.offsetWidth, paletteTarget.offsetHeight);
image.src = paletteTarget.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '');
var VAR_MATCH_REGEX = /\{\{\s*(.*?)\s*\}\}/g;
function _valueToString (value) {
switch (typeof value) {
case 'string':
case 'number':
case 'boolean':
return value;
case 'object':
try {
// null is an object but is falsy. Swallow it.
return value === null ? '' : JSON.stringify(value);
} catch (jsonError) {
return '{...}';
}
return new colorThief_min().getPalette(image, 3)
default:
// Anything else will be replaced with an empty string
// For example: undefined, Symbol, etc.
return '';
}
}
/**
* Returns the rgba color from current color with applied opacity
* @param {Array} color
* @param {String | Number} opacity
* @return {string}
* @private
*/
function __getRgbaColor (color, opacity) {
var
r = color[0],
g = color[1],
b = color[2];
return ("rgba(" + r + ", " + g + ", " + b + ", " + opacity + ")")
/**
* Recursively goes through an object trying to resolve a path.
*
* @param {Object} scope - The object to traverse (in each recursive call we dig into this object)
* @param {string[]} path - An array of property names to traverse one-by-one
* @param {number} [pathIndex=0] - The current index in the path array
*/
function _recursivePathResolver(scope, path, pathIndex) {
if ( pathIndex === void 0 ) pathIndex = 0;
if (typeof scope !== 'object' || scope === null || scope === undefined) {
return '';
}
/**
* Adds boxShadow to an element.
* Required the color and the element to attach to
* @param {Array} colors - Array of RGB Colors
* @param {Object} obj - Object that holds the hoverTarget and image
* @param {HTMLElement} obj.hoverTarget - Reference to the hoverTarget dom element
* @param {HTMLElement} obj.image - Reference to the image dom element
* @param {Array} obj.colors - Array of RGB Colors
* @param {String} obj.rgbaColor - rgba transformed color with opacity in mind.
* @param {String} obj.rgbaColorSecondary - rgba transformed color with secondary opacity in mind.
* @private
*/
function __addBoxShadowToElementData (colors, obj) {
var ref = self.options;
var opacity = ref.opacity;
var opacitySecondary = ref.opacitySecondary;
var boxShadowTemplate = ref.boxShadowTemplate;
var color = colors[self.options.colorIndexToUse],
rgbaColor = __getRgbaColor(color, opacity),
rgbaColorSecondary = __getRgbaColor(color, opacitySecondary);
var boxShadow;
if (!boxShadowTemplate) {
boxShadow = "0 2px 2px " + rgbaColor + ", 0 4px 4px " + rgbaColor + ", 0 8px 8px " + rgbaColor + ", 0 16px 16px " + rgbaColor + ", 0 32px 32px " + rgbaColorSecondary + ", 0 64px 64px " + rgbaColorSecondary;
var varName = path[pathIndex];
var value = scope[varName];
if (pathIndex === path.length - 1) {
// It's a leaf, return whatever it is
return value;
}
return _recursivePathResolver(value, path, ++pathIndex);
}
function defaultResolver(varName, view) {
return _recursivePathResolver(view, varName.split('.'));
}
/**
* Replaces every {{variable}} inside the template with values provided by view.
*
* @param {string} template - The template containing one or more {{variableNames}} every variable
* names that is used in the template. If it's omitted, it'll be assumed an empty object.
* @param {Object} [view={}] - An object containing values for every variable names that is used in
* the template. If it's omitted, it'll be set to an empty object essentially removing all
* {{varName}}s in the template.
* @param {ResolverFn} [resolver] - An optional function that will be
* called for every {{varName}} to generate a value. If the resolver throws an error
* we'll proceed with the default value resolution algorithm (find the value from the view
* object).
* @returns {string} - Template where its variable names replaced with
* corresponding values. If a value is not found or is invalid, it will
* be assumed empty string ''. If the value is an object itself, it'll
* be stringified by JSON.
* In case of a JSON stringify error the result will look like "{...}".
*/
function render (template, view, resolver) {
if ( view === void 0 ) view = {};
if ( resolver === void 0 ) resolver = defaultResolver;
// don't touch the template if it is not a string
if (typeof template !== 'string') {
return template;
}
return template.replace(VAR_MATCH_REGEX, function (match, varName) {
try {
// defaultResolver never throws
return _valueToString(resolver(varName, view));
} catch (e) {
// if your resolver throws, we proceed with the default resolver
return _valueToString(defaultResolver(varName, view));
}
});
}
var render_1$1 = render;
var render_1 = render_1$1;
var index$2 = function isMergeableObject(value) {
return isNonNullObject(value) && isNotSpecial(value)
};
function isNonNullObject(value) {
return !!value && typeof value === 'object'
}
function isNotSpecial(value) {
var stringValue = Object.prototype.toString.call(value);
return stringValue !== '[object RegExp]'
&& stringValue !== '[object Date]'
}
function emptyTarget(val) {
return Array.isArray(val) ? [] : {}
}
function cloneIfNecessary(value, optionsArgument) {
var clone = optionsArgument && optionsArgument.clone === true;
return (clone && index$2(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value
}
function defaultArrayMerge(target, source, optionsArgument) {
var destination = target.slice();
source.forEach(function(e, i) {
if (typeof destination[i] === 'undefined') {
destination[i] = cloneIfNecessary(e, optionsArgument);
} else if (index$2(e)) {
destination[i] = deepmerge(target[i], e, optionsArgument);
} else if (target.indexOf(e) === -1) {
destination.push(cloneIfNecessary(e, optionsArgument));
}
});
return destination
}
function mergeObject(target, source, optionsArgument) {
var destination = {};
if (index$2(target)) {
Object.keys(target).forEach(function(key) {
destination[key] = cloneIfNecessary(target[key], optionsArgument);
});
}
Object.keys(source).forEach(function(key) {
if (!index$2(source[key]) || !target[key]) {
destination[key] = cloneIfNecessary(source[key], optionsArgument);
} else {
destination[key] = deepmerge(target[key], source[key], optionsArgument);
}
});
return destination
}
function deepmerge(target, source, optionsArgument) {
var array = Array.isArray(source);
var options = optionsArgument || { arrayMerge: defaultArrayMerge };
var arrayMerge = options.arrayMerge || defaultArrayMerge;
if (array) {
return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument)
} else {
boxShadow = boxShadowTemplate.replace('{color}', rgbaColor).replace('{colorSecondary}', rgbaColorSecondary);
return mergeObject(target, source, optionsArgument)
}
obj.colors = colors;
obj.rgbaColor = rgbaColor;
obj.rgbaColorSecondary = rgbaColorSecondary;
obj.boxShadow = boxShadow;
}
}
var isInitialized = false;
deepmerge.all = function deepmergeAll(array, optionsArgument) {
if (!Array.isArray(array) || array.length < 2) {
throw new Error('first argument should be an array with at least two elements')
}
// we are sure there are at least 2 values, so it is safe to have no initial value
return array.reduce(function(prev, next) {
return deepmerge(prev, next, optionsArgument)
})
};
var index$2$1 = deepmerge;
/**
* Creates a palettify instance
* @module palettify
* @return palettify
*/
function createPalettify () {
var
isInitialized = false,
__selector = null;
/**
* @typedef {Object} paletteObj
* @property {HTMLElement} eventTarget
* @property {HTMLElement} styleTarget
* @property {HTMLElement} image
* @property {PaletteObject} palette
* @property {Function} enterHandler
* @property {Function} leaveHandler
*/
/**
* @typedef {Object} PaletteObject
* @property {Array} original
* @property {Array} rgb
* @property {Array} rgba
*/
var
/**
* Default options to extend
* @typedef {Object} DefaultOptions
* @property {String|HTMLElement} selector - The element that holds all your images and eventTargets.
* @property {String} eventTarget - The event target to attach event listeners to
* @property {String} image - The image to sample
* @property {String} styleTarget - The element to apply styling to. Defaults to image
* @property {Object} contrastColors - Light and Dark colors, based on brightness of each color in the palette
* @property {String} contrastColors.light - Light inverted color
* @property {String} contrastColors.dark - Dark inverted color
* @property {String} activeClass - CSS class to apply on each enterEvent
* @property {String} readyClass - CSS class to apply when palettify is ready
* @property {Number} colorsToExtract - Colors to extract
* @property {String | Array} enterEvent - Event or Array of events to apply listeners to for each enterCallback
* @property {string | Array} leaveEvent - Event or Array of events to apply listeners to for each leaveCallback
* @property {Object} styles - Collection of static and dynamic styles
* @property {Array<number>} styles.opacities - Array of opacities
* @property {Object} styles.static - Object containing valid css styles to apply to styleTarget on ready
* @property {Object} styles.dynamic - Object containing valid css styles to apply to styleTarget on each enterEvent
* @property {Function} beforeEnterCallback - Callback called before the enter event
* @property {Function} afterEnterCallback - Callback called after the enter event
* @property {Function} beforeLeaveCallback - Callback called before the leave event
* @property {Function} afterLeaveCallback - Callback called after the enter event
* @property {Function} onReadyCallback - Callback called after palettify is ready.
*/
__defaults = {
hoverTarget: Error('Please provide hoverTarget'),
imageTarget: Error('Please provide ImageSrc'),
attachBoxShadowTo: null,
opacity: 0.2,
opacitySecondary: 0.2,
hoverClass: 'box-shadow-attached',
colorIndexToUse: 0,
selector: Error('Please provide а selector in your options.'),
eventTarget: Error('Please provide an eventTarget as a parent for your image in the options.'),
image: Error('Please provide an image to sample.'),
styleTarget: null,
contrastColors: {
light: '#fff',
dark: '#000'
},
activeClass: 'palettify--active',
readyClass: 'palettify--ready',
colorsToExtract: 3,
enterEvent: 'mouseenter',
leaveEvent: 'mouseleave',
boxShadowTemplate: ''
styles: {
opacities: [0.5, 0.5, 0.5],
static: {},
dynamic: {}
},
staticCallback: null,
beforeEnterCallback: null,
afterEnterCallback: null,
beforeLeaveCallback: null,
afterLeaveCallback: null,
onReadyCallback: null
},
/**
* @typedef {Object} palettify
* @type Object
* @property {DefaultOptions} options
* @property {Array} data
* @property {function} collectElements
* @property {function} extractColorsAndAttachStyles
* @property {function} generateEnterHandler
* @property {function} generateLeaveHandler
* @property {function} attachEventListeners
* @property {function} detachEventListeners
* @property {function} init
* @property {function} destroy
* @property {function} reInit
* @property {function} cleanUp
* @property {function} setOptions
* @property {function} isInitialized
*/
self = {
/**
* Palettify options
* @name palettify#options
* @type DefaultOptions
*/
options: {},
elements: [],
/**
* Gather all needed elements and push into an the `elements` array
* Holds a collection of {@link paletteObj} objects.
* @type Array<paletteObj>
* @name palettify#data
*/
data: [],
/**
* Gather all needed elements and push into an the {@see palettify#data}
* @type function
* @name palettify#collectElements
*/
collectElements: function collectElements () {
var hoverTargetsCollection = self.options.hoverTarget;
if (typeof hoverTargetsCollection === 'string') {
hoverTargetsCollection = document.querySelectorAll(hoverTargetsCollection);
}
if (!hoverTargetsCollection.length) { return }
for (var i = 0; i < hoverTargetsCollection.length; i++) {
var
eventTargetsCollection = '';
__selector = typeof self.options.selector === 'string' ? document.querySelector(self.options.selector) : self.options.selector;
if (!__selector) { throw new Error('Selector ' + self.options.selector + ' does not exist') }
eventTargetsCollection = __selector.querySelectorAll(self.options.eventTarget);
[].slice.call(eventTargetsCollection, 0).forEach(function (eventTarget) {
var
element = hoverTargetsCollection[i],
image = element.querySelector(self.options.imageTarget),
image = eventTarget.querySelector(self.options.image),
styleTarget = self.options.styleTarget === true ? eventTarget : eventTarget.querySelector(self.options.styleTarget || self.options.image),
// Create the main object it self.
obj = {
hoverTarget: element,
image: image
eventTarget: eventTarget,
styleTarget: styleTarget,
image: image,
palette: {
original: [],
rgb: [],
rgba: []
}
};
self.elements.push(obj);
}
self.data.push(obj);
});
},
/**
* Attaches boxShadow to the collection of Images
* Extracts colors and attaches static styles to each styleTarget {@see palettify#options.styleTarget}
* Adds ready class and calls onReadyCallback when done
* @function
* @name palettify#extractColorsAndAttachStyles
*/
attachBoxShadowToCollection: function attachBoxShadowToCollection () {
self.elements.forEach(function (obj) {
var color = __extractColors(obj.image);
__addBoxShadowToElementData(color, obj);
extractColorsAndAttachStyles: function extractColorsAndAttachStyles (skipCallbacks) {
if ( skipCallbacks === void 0 ) skipCallbacks = false;
var promises = [];
self.data.forEach(function (obj, index) {
promises[index] = __extractColors(obj.image, self.options.colorsToExtract).then(function (colors) {
obj.palette.original = colors;
obj.palette.rgb = __opacifyPalette(obj.palette.original, []);
obj.palette.rgba = __opacifyPalette(obj.palette.original, self.options.styles.opacities);
obj.palette.contrastColors = __getInvertedColors(obj.palette.original, self.options.contrastColors);
__attachStylesToElement({
target: obj.styleTarget,
styles: self.options.styles.static,
palette: obj.palette,
staticCallback: self.options.staticCallback,
obj: obj
});
});
});
if (!skipCallbacks) {
Promise.all(promises).then(function (values) {
__selector.classList.add(self.options.readyClass);
typeof self.options.onReadyCallback === 'function' && self.options.onReadyCallback.call(self, self);
});
}
},
/**
* Enter event listener callback
* Adds the boxShadow to the target
* @param event
* Generates the enter event listener callback
* Attaches dynamicStyles {@see palettify#options.styles.dynamic} to styleTarget
* @function
* @param {paletteObj} obj
* @name palettify#generateEnterHandler
* @return function
*/
enterHandler: function enterHandler (obj) {
generateEnterHandler: function generateEnterHandler (obj) {
return function (event) {
var target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget);
if (target) {
target.classList.add(self.options.hoverClass);
target.style.boxShadow = obj.boxShadow;
if (obj.styleTarget) {
if (typeof self.options.beforeEnterCallback === 'function') { self.options.beforeEnterCallback.call(obj.styleTarget, obj, self.options, event); }
obj.styleTarget.classList.add(self.options.activeClass);
__attachStylesToElement({
target: obj.styleTarget,
styles: self.options.styles.dynamic,
palette: obj.palette,
obj: obj
});
if (typeof self.options.afterEnterCallback === 'function') { self.options.afterEnterCallback.call(obj.styleTarget, obj, self.options, event); }
}

@@ -180,12 +419,17 @@ }

/**
* Leave Event listener callback
* Removes the Box shadow from the target
* @param event
* Generates the leave event listener callback
* Removes all dynamicStyles
* @function
* @param {paletteObj} obj
* @name palettify#generateLeaveHandler
* @return function
*/
leaveHandler: function leaveHandler (obj) {
generateLeaveHandler: function generateLeaveHandler (obj) {
return function (event) {
var target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget);
var target = obj.styleTarget;
if (target) {
target.classList.remove(self.options.hoverClass);
target.style.boxShadow = '';
if (typeof self.options.beforeLeaveCallback === 'function') { self.options.beforeLeaveCallback.call(obj.styleTarget, obj, self.options, event); }
target.classList.remove(self.options.activeClass);
__removeDynamicStylesFromElement(obj.styleTarget, self.options.styles.dynamic, self.options.styles.static, obj.palette);
if (typeof self.options.afterLeaveCallback === 'function') { self.options.afterLeaveCallback.call(obj.styleTarget, obj, self.options, event); }
}

@@ -195,3 +439,5 @@ }

/**
* Attaches Event listeners to the hoverTargets
* Attaches Event listeners to the eventTargets
* @function
* @name palettify#attachEventListeners
*/

@@ -202,11 +448,23 @@ attachEventListeners: function attachEventListeners () {

var leaveEvent = ref.leaveEvent;
self.elements.forEach(function (obj) {
obj.enterHandler = self.enterHandler(obj);
obj.leaveHandler = self.leaveHandler(obj);
obj.hoverTarget.addEventListener(enterEvent, obj.enterHandler, false);
obj.hoverTarget.addEventListener(leaveEvent, obj.leaveHandler, false);
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent;
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent;
self.data.forEach(function (obj) {
obj.enterHandler = self.generateEnterHandler(obj);
obj.leaveHandler = self.generateLeaveHandler(obj);
enterEvent.forEach(function (event) {
obj.eventTarget.addEventListener(event, obj.enterHandler, false);
});
leaveEvent.forEach(function (event) {
obj.eventTarget.addEventListener(event, obj.leaveHandler, false);
});
});
},
/**
* Detaches event listeners from hoverTargets
* Detaches event listeners from eventTargets
* @function
* @name palettify#detachEventListeners
*/

@@ -217,5 +475,13 @@ detachEventListeners: function detachEventListeners () {

var leaveEvent = ref.leaveEvent;
self.elements.forEach(function (obj) {
obj.hoverTarget.removeEventListener(enterEvent, obj.enterHandler, false);
obj.hoverTarget.removeEventListener(leaveEvent, obj.leaveHandler, false);
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent;
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent;
self.data.forEach(function (obj) {
enterEvent.forEach(function (event) {
obj.eventTarget.removeEventListener(event, obj.enterHandler, false);
});
leaveEvent.forEach(function (event) {
obj.eventTarget.removeEventListener(event, obj.leaveHandler, false);
});
});

@@ -226,2 +492,6 @@ },

* Gets fired when creating Palettify in the first place
* @constructs
* @param {DefaultOptions} opts - Options to pass to palettify
* @name palettify#init
* @return palettify
*/

@@ -231,7 +501,7 @@ init: function init (opts) {

self.options = Object.assign({}, __defaults, opts);
__mergeOptions(opts);
self.collectElements();
// Attach boxShadow's to all images
self.attachBoxShadowToCollection();
// Collect colors and attach boxShadow's to all images
self.extractColorsAndAttachStyles();
// Attach event listeners to all hovered elements.

@@ -246,6 +516,12 @@ self.attachEventListeners();

* Destroys the Palettify and cleans up after it self.
* @function
* @name palettify#destroy
* @param {Boolean} [cleanUp = true]
*/
destroy: function destroy () {
destroy: function destroy (cleanUp) {
if ( cleanUp === void 0 ) cleanUp = true;
self.detachEventListeners();
self.elements = [];
cleanUp && self.cleanUp();
self.data = [];
isInitialized = false;

@@ -256,2 +532,4 @@ },

* Destroy and then reinitialize it.
* @function
* @name palettify#reInit
*/

@@ -263,7 +541,21 @@ reInit: function reInit$1 () {

/**
* Cleans up the dom after the plugin is destroyed. Removes all styles.static
* @function
* @name palettify#cleanUp
*/
cleanUp: function cleanUp$1 () {
self.data.forEach(function (obj) {
for (var prop in self.options.styles.static) {
if (self.options.styles.static.hasOwnProperty(prop)) {
obj.styleTarget.style[prop] = '';
}
}
});
},
/**
* Set new options. Merges then with the old ones.
* Takes an options object and a reInit boolean.
* reInit defaults to True
* @param {Object} options - New options to override the old ones
* @param {Boolean} reInit - Should the plugin reInit? Defaults to true
* @param {Boolean} [reInit = true] - Should the plugin reInit? Defaults to true
* @function
* @name palettify#setOptions
*/

@@ -278,2 +570,3 @@ setOptions: function setOptions (options, reInit) {

* Is the plugin initialized
* @name palettify#isInitialized
* @return {boolean}

@@ -286,5 +579,167 @@ */

/**
* Extract the colors from image tag or Background-image inline style
* @param {HTMLElement} paletteTarget - The palette target to get the colors form
* @param {Number} colorsToExtract - Number of colors to extract
* @return {Promise} Returns promise when image is loaded with an array of RGB colors
* @private
*/
function __extractColors (paletteTarget, colorsToExtract) {
var image = __sanitizeImage(paletteTarget);
return new Promise(function (resolve) {
if (!isImageLoaded(image)) {
image.onload = function () {
resolve(new colorThief_min().getPalette(image, colorsToExtract));
};
} else {
resolve(new colorThief_min().getPalette(image, colorsToExtract));
}
})
}
/**
* Transform all colors in the palette to RGBA colors using the supplied opacities in {@see palettify#options.opacities}
* @param {Array} palette - Color palette of the current obj
* @param {Array} opacities - Array of opacities for each color
* @private
*/
function __opacifyPalette (palette, opacities) {
return palette.map(function (color, i) {
var opacity = 1;
if (Array.isArray(opacities) && opacities[i]) {
opacity = opacities[i];
}
return __getRgbaColor(color, opacity)
})
}
/**
* Returns the rgba color with applied opacity
* @param {Array} color
* @param {String | Number} opacity
* @return {string}
* @private
*/
function __getRgbaColor (color, opacity) {
var
r = color[0],
g = color[1],
b = color[2];
return ("rgba(" + r + ", " + g + ", " + b + ", " + opacity + ")")
}
/**
* Adds styles to an element.
* @param target
* @param {Object} styles
* @param palette
* @param staticCallback
* @param obj
* @private
*/
function __attachStylesToElement (ref) {
var target = ref.target;
var styles = ref.styles;
var palette = ref.palette;
var staticCallback = ref.staticCallback; if ( staticCallback === void 0 ) staticCallback = null;
var obj = ref.obj;
for (var prop in styles) {
if (styles.hasOwnProperty(prop)) {
target.style[prop] = render_1(styles[prop], palette);
}
}
staticCallback && staticCallback.call(target, obj, self.options);
}
/**
* Removes all dynamic styles from an element.
* If the staticStyle has the same prop as dynamicStyle, we set the prop to be the static style.
* @param {HTMLElement} target - The Target to remove styles from
* @param {Object} dynamicStyles - Dynamic styles to apply
* @param {Object} staticStyles - Static styles to apply
* @param {Array} palette - Array of available palettes
* @private
*/
function __removeDynamicStylesFromElement (target, dynamicStyles, staticStyles, palette) {
for (var prop in dynamicStyles) {
if (dynamicStyles.hasOwnProperty(prop) && !staticStyles.hasOwnProperty(prop)) {
target.style[prop] = '';
} else if (staticStyles.hasOwnProperty(prop)) {
target.style[prop] = render_1(staticStyles[prop], palette);
}
}
}
/**
* Merges the options and throws errors where necessary
* @param {DefaultOptions} options
* @private
*/
function __mergeOptions (options) {
self.options = index$2$1(__defaults, options, {clone: true, arrayMerge: __arrayMerge});
Object.keys(self.options).forEach(function (opt) {
if (self.options[opt] instanceof Error) {
throw self.options[opt]
}
});
}
function __getBrightness (rgb) {
return (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000
}
function __isDark (color) {
return __getBrightness(color) < 170 // 128 by default, but 170 gives better results
}
function __getInvertedColors (palette, colors) {
return palette.map(function (color) { return __isDark(color) ? colors.light : colors.dark; })
}
function __getUrl (url) {
var el = document.createElement('a');
el.href = url;
return el
}
function __isCORS (src) {
return document.location.host !== __getUrl(src).host
}
function isImageLoaded (img) {
if (!img.complete) { return false }
if (typeof img.naturalWidth !== 'undefined' && img.naturalWidth === 0) {
return false
}
// No other way of checking: assume it's ok.
return true
}
function __sanitizeImage (imgElement) {
var
cachedImg = imgElement,
isNotIMG = cachedImg.tagName !== 'IMG',
src = isNotIMG ? imgElement.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '') : imgElement.src,
isCors = __isCORS(src);
if (!cachedImg) { throw Error('Target is not an element', cachedImg) }
// Our sample is not a img tag so we try to get its background image.
if (isNotIMG || (isCors && !cachedImg.crossOrigin)) {
if (isNotIMG && !cachedImg.style.backgroundImage) { throw Error('Tag provided is not an image and does not have a background-image style attached to it.') }
cachedImg = new Image(imgElement.naturalWidth, imgElement.naturalHeight);
isCors && (cachedImg.crossOrigin = 'anonymous');
cachedImg.src = src;
}
return cachedImg
}
function __arrayMerge (destArray, sourceArray, opts) {
return sourceArray
}
return self
}
module.exports = palettify;
module.exports = createPalettify;
/*!
* palettify v0.0.0
* palettify v1.0.0
* (c) 2017 Dobromir Hristov

@@ -44,137 +44,376 @@ * Released under the MIT License.

/**
* palettify
* @param {Object} opts
* @param {String | NodeList} opts.imageTarget
* @param {String | NodeList} opts.hoverTarget
* @param {String} opts.attachBoxShadowTo
* @param {String | Number} opts.opacity
* @param {String} opts.opacitySecondary
* @param {String} opts.colorIndexToUse
* @param {String} opts.boxShadowTemplate - Provide a boxShadow template to apply. '0 2px 2px {color}, 3px 3px {colorSecondary}'
* @callback ResolverFn
* @param {string} varName - variable name before being parsed.
* For example: {a.b.c} -> 'a.b.c', { x } -> 'x'
* @param {Object} view - the view object that was passed to .render() function
* @returns {string|number|boolean|Object|undefined} the value to be
* interpolated. If the function returns undefined, the value resolution
* algorithm will go ahead with the default behaviour (resolving the
* variable name from the provided object).
*/
function palettify () {
/**
* Extract the colors from image tag or Background-image inline style
* @param {HTMLElement} paletteTarget - The palette target to get the colors form
* @return {Array} Returns an object with RGB colors
* @private
*/
function __extractColors (paletteTarget) {
var image = paletteTarget;
if (!paletteTarget) { throw Error('Target is not an element', paletteTarget) }
// Our sample is not a img tag so we try to get its background image.
if (paletteTarget.tagName !== 'IMG' && paletteTarget.style.backgroundImage) { Error('Tag provided is not an image and does not have a background-image style attached to it.'); }
// Its not an IMG tag so we try to crate one under the hood.
if (paletteTarget.tagName !== 'IMG') {
image = new Image(paletteTarget.offsetWidth, paletteTarget.offsetHeight);
image.src = paletteTarget.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '');
var VAR_MATCH_REGEX = /\{\{\s*(.*?)\s*\}\}/g;
function _valueToString (value) {
switch (typeof value) {
case 'string':
case 'number':
case 'boolean':
return value;
case 'object':
try {
// null is an object but is falsy. Swallow it.
return value === null ? '' : JSON.stringify(value);
} catch (jsonError) {
return '{...}';
}
return new colorThief_min().getPalette(image, 3)
default:
// Anything else will be replaced with an empty string
// For example: undefined, Symbol, etc.
return '';
}
}
/**
* Returns the rgba color from current color with applied opacity
* @param {Array} color
* @param {String | Number} opacity
* @return {string}
* @private
*/
function __getRgbaColor (color, opacity) {
var
r = color[0],
g = color[1],
b = color[2];
return ("rgba(" + r + ", " + g + ", " + b + ", " + opacity + ")")
/**
* Recursively goes through an object trying to resolve a path.
*
* @param {Object} scope - The object to traverse (in each recursive call we dig into this object)
* @param {string[]} path - An array of property names to traverse one-by-one
* @param {number} [pathIndex=0] - The current index in the path array
*/
function _recursivePathResolver(scope, path, pathIndex) {
if ( pathIndex === void 0 ) pathIndex = 0;
if (typeof scope !== 'object' || scope === null || scope === undefined) {
return '';
}
/**
* Adds boxShadow to an element.
* Required the color and the element to attach to
* @param {Array} colors - Array of RGB Colors
* @param {Object} obj - Object that holds the hoverTarget and image
* @param {HTMLElement} obj.hoverTarget - Reference to the hoverTarget dom element
* @param {HTMLElement} obj.image - Reference to the image dom element
* @param {Array} obj.colors - Array of RGB Colors
* @param {String} obj.rgbaColor - rgba transformed color with opacity in mind.
* @param {String} obj.rgbaColorSecondary - rgba transformed color with secondary opacity in mind.
* @private
*/
function __addBoxShadowToElementData (colors, obj) {
var ref = self.options;
var opacity = ref.opacity;
var opacitySecondary = ref.opacitySecondary;
var boxShadowTemplate = ref.boxShadowTemplate;
var color = colors[self.options.colorIndexToUse],
rgbaColor = __getRgbaColor(color, opacity),
rgbaColorSecondary = __getRgbaColor(color, opacitySecondary);
var boxShadow;
if (!boxShadowTemplate) {
boxShadow = "0 2px 2px " + rgbaColor + ", 0 4px 4px " + rgbaColor + ", 0 8px 8px " + rgbaColor + ", 0 16px 16px " + rgbaColor + ", 0 32px 32px " + rgbaColorSecondary + ", 0 64px 64px " + rgbaColorSecondary;
var varName = path[pathIndex];
var value = scope[varName];
if (pathIndex === path.length - 1) {
// It's a leaf, return whatever it is
return value;
}
return _recursivePathResolver(value, path, ++pathIndex);
}
function defaultResolver(varName, view) {
return _recursivePathResolver(view, varName.split('.'));
}
/**
* Replaces every {{variable}} inside the template with values provided by view.
*
* @param {string} template - The template containing one or more {{variableNames}} every variable
* names that is used in the template. If it's omitted, it'll be assumed an empty object.
* @param {Object} [view={}] - An object containing values for every variable names that is used in
* the template. If it's omitted, it'll be set to an empty object essentially removing all
* {{varName}}s in the template.
* @param {ResolverFn} [resolver] - An optional function that will be
* called for every {{varName}} to generate a value. If the resolver throws an error
* we'll proceed with the default value resolution algorithm (find the value from the view
* object).
* @returns {string} - Template where its variable names replaced with
* corresponding values. If a value is not found or is invalid, it will
* be assumed empty string ''. If the value is an object itself, it'll
* be stringified by JSON.
* In case of a JSON stringify error the result will look like "{...}".
*/
function render (template, view, resolver) {
if ( view === void 0 ) view = {};
if ( resolver === void 0 ) resolver = defaultResolver;
// don't touch the template if it is not a string
if (typeof template !== 'string') {
return template;
}
return template.replace(VAR_MATCH_REGEX, function (match, varName) {
try {
// defaultResolver never throws
return _valueToString(resolver(varName, view));
} catch (e) {
// if your resolver throws, we proceed with the default resolver
return _valueToString(defaultResolver(varName, view));
}
});
}
var render_1$1 = render;
var render_1 = render_1$1;
var index$2 = function isMergeableObject(value) {
return isNonNullObject(value) && isNotSpecial(value)
};
function isNonNullObject(value) {
return !!value && typeof value === 'object'
}
function isNotSpecial(value) {
var stringValue = Object.prototype.toString.call(value);
return stringValue !== '[object RegExp]'
&& stringValue !== '[object Date]'
}
function emptyTarget(val) {
return Array.isArray(val) ? [] : {}
}
function cloneIfNecessary(value, optionsArgument) {
var clone = optionsArgument && optionsArgument.clone === true;
return (clone && index$2(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value
}
function defaultArrayMerge(target, source, optionsArgument) {
var destination = target.slice();
source.forEach(function(e, i) {
if (typeof destination[i] === 'undefined') {
destination[i] = cloneIfNecessary(e, optionsArgument);
} else if (index$2(e)) {
destination[i] = deepmerge(target[i], e, optionsArgument);
} else if (target.indexOf(e) === -1) {
destination.push(cloneIfNecessary(e, optionsArgument));
}
});
return destination
}
function mergeObject(target, source, optionsArgument) {
var destination = {};
if (index$2(target)) {
Object.keys(target).forEach(function(key) {
destination[key] = cloneIfNecessary(target[key], optionsArgument);
});
}
Object.keys(source).forEach(function(key) {
if (!index$2(source[key]) || !target[key]) {
destination[key] = cloneIfNecessary(source[key], optionsArgument);
} else {
destination[key] = deepmerge(target[key], source[key], optionsArgument);
}
});
return destination
}
function deepmerge(target, source, optionsArgument) {
var array = Array.isArray(source);
var options = optionsArgument || { arrayMerge: defaultArrayMerge };
var arrayMerge = options.arrayMerge || defaultArrayMerge;
if (array) {
return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument)
} else {
boxShadow = boxShadowTemplate.replace('{color}', rgbaColor).replace('{colorSecondary}', rgbaColorSecondary);
return mergeObject(target, source, optionsArgument)
}
obj.colors = colors;
obj.rgbaColor = rgbaColor;
obj.rgbaColorSecondary = rgbaColorSecondary;
obj.boxShadow = boxShadow;
}
}
var isInitialized = false;
deepmerge.all = function deepmergeAll(array, optionsArgument) {
if (!Array.isArray(array) || array.length < 2) {
throw new Error('first argument should be an array with at least two elements')
}
// we are sure there are at least 2 values, so it is safe to have no initial value
return array.reduce(function(prev, next) {
return deepmerge(prev, next, optionsArgument)
})
};
var index$2$1 = deepmerge;
/**
* Creates a palettify instance
* @module palettify
* @return palettify
*/
function createPalettify () {
var
isInitialized = false,
__selector = null;
/**
* @typedef {Object} paletteObj
* @property {HTMLElement} eventTarget
* @property {HTMLElement} styleTarget
* @property {HTMLElement} image
* @property {PaletteObject} palette
* @property {Function} enterHandler
* @property {Function} leaveHandler
*/
/**
* @typedef {Object} PaletteObject
* @property {Array} original
* @property {Array} rgb
* @property {Array} rgba
*/
var
/**
* Default options to extend
* @typedef {Object} DefaultOptions
* @property {String|HTMLElement} selector - The element that holds all your images and eventTargets.
* @property {String} eventTarget - The event target to attach event listeners to
* @property {String} image - The image to sample
* @property {String} styleTarget - The element to apply styling to. Defaults to image
* @property {Object} contrastColors - Light and Dark colors, based on brightness of each color in the palette
* @property {String} contrastColors.light - Light inverted color
* @property {String} contrastColors.dark - Dark inverted color
* @property {String} activeClass - CSS class to apply on each enterEvent
* @property {String} readyClass - CSS class to apply when palettify is ready
* @property {Number} colorsToExtract - Colors to extract
* @property {String | Array} enterEvent - Event or Array of events to apply listeners to for each enterCallback
* @property {string | Array} leaveEvent - Event or Array of events to apply listeners to for each leaveCallback
* @property {Object} styles - Collection of static and dynamic styles
* @property {Array<number>} styles.opacities - Array of opacities
* @property {Object} styles.static - Object containing valid css styles to apply to styleTarget on ready
* @property {Object} styles.dynamic - Object containing valid css styles to apply to styleTarget on each enterEvent
* @property {Function} beforeEnterCallback - Callback called before the enter event
* @property {Function} afterEnterCallback - Callback called after the enter event
* @property {Function} beforeLeaveCallback - Callback called before the leave event
* @property {Function} afterLeaveCallback - Callback called after the enter event
* @property {Function} onReadyCallback - Callback called after palettify is ready.
*/
__defaults = {
hoverTarget: Error('Please provide hoverTarget'),
imageTarget: Error('Please provide ImageSrc'),
attachBoxShadowTo: null,
opacity: 0.2,
opacitySecondary: 0.2,
hoverClass: 'box-shadow-attached',
colorIndexToUse: 0,
selector: Error('Please provide а selector in your options.'),
eventTarget: Error('Please provide an eventTarget as a parent for your image in the options.'),
image: Error('Please provide an image to sample.'),
styleTarget: null,
contrastColors: {
light: '#fff',
dark: '#000'
},
activeClass: 'palettify--active',
readyClass: 'palettify--ready',
colorsToExtract: 3,
enterEvent: 'mouseenter',
leaveEvent: 'mouseleave',
boxShadowTemplate: ''
styles: {
opacities: [0.5, 0.5, 0.5],
static: {},
dynamic: {}
},
staticCallback: null,
beforeEnterCallback: null,
afterEnterCallback: null,
beforeLeaveCallback: null,
afterLeaveCallback: null,
onReadyCallback: null
},
/**
* @typedef {Object} palettify
* @type Object
* @property {DefaultOptions} options
* @property {Array} data
* @property {function} collectElements
* @property {function} extractColorsAndAttachStyles
* @property {function} generateEnterHandler
* @property {function} generateLeaveHandler
* @property {function} attachEventListeners
* @property {function} detachEventListeners
* @property {function} init
* @property {function} destroy
* @property {function} reInit
* @property {function} cleanUp
* @property {function} setOptions
* @property {function} isInitialized
*/
self = {
/**
* Palettify options
* @name palettify#options
* @type DefaultOptions
*/
options: {},
elements: [],
/**
* Gather all needed elements and push into an the `elements` array
* Holds a collection of {@link paletteObj} objects.
* @type Array<paletteObj>
* @name palettify#data
*/
data: [],
/**
* Gather all needed elements and push into an the {@see palettify#data}
* @type function
* @name palettify#collectElements
*/
collectElements: function collectElements () {
var hoverTargetsCollection = self.options.hoverTarget;
if (typeof hoverTargetsCollection === 'string') {
hoverTargetsCollection = document.querySelectorAll(hoverTargetsCollection);
}
if (!hoverTargetsCollection.length) { return }
for (var i = 0; i < hoverTargetsCollection.length; i++) {
var
eventTargetsCollection = '';
__selector = typeof self.options.selector === 'string' ? document.querySelector(self.options.selector) : self.options.selector;
if (!__selector) { throw new Error('Selector ' + self.options.selector + ' does not exist') }
eventTargetsCollection = __selector.querySelectorAll(self.options.eventTarget);
[].slice.call(eventTargetsCollection, 0).forEach(function (eventTarget) {
var
element = hoverTargetsCollection[i],
image = element.querySelector(self.options.imageTarget),
image = eventTarget.querySelector(self.options.image),
styleTarget = self.options.styleTarget === true ? eventTarget : eventTarget.querySelector(self.options.styleTarget || self.options.image),
// Create the main object it self.
obj = {
hoverTarget: element,
image: image
eventTarget: eventTarget,
styleTarget: styleTarget,
image: image,
palette: {
original: [],
rgb: [],
rgba: []
}
};
self.elements.push(obj);
}
self.data.push(obj);
});
},
/**
* Attaches boxShadow to the collection of Images
* Extracts colors and attaches static styles to each styleTarget {@see palettify#options.styleTarget}
* Adds ready class and calls onReadyCallback when done
* @function
* @name palettify#extractColorsAndAttachStyles
*/
attachBoxShadowToCollection: function attachBoxShadowToCollection () {
self.elements.forEach(function (obj) {
var color = __extractColors(obj.image);
__addBoxShadowToElementData(color, obj);
extractColorsAndAttachStyles: function extractColorsAndAttachStyles (skipCallbacks) {
if ( skipCallbacks === void 0 ) skipCallbacks = false;
var promises = [];
self.data.forEach(function (obj, index) {
promises[index] = __extractColors(obj.image, self.options.colorsToExtract).then(function (colors) {
obj.palette.original = colors;
obj.palette.rgb = __opacifyPalette(obj.palette.original, []);
obj.palette.rgba = __opacifyPalette(obj.palette.original, self.options.styles.opacities);
obj.palette.contrastColors = __getInvertedColors(obj.palette.original, self.options.contrastColors);
__attachStylesToElement({
target: obj.styleTarget,
styles: self.options.styles.static,
palette: obj.palette,
staticCallback: self.options.staticCallback,
obj: obj
});
});
});
if (!skipCallbacks) {
Promise.all(promises).then(function (values) {
__selector.classList.add(self.options.readyClass);
typeof self.options.onReadyCallback === 'function' && self.options.onReadyCallback.call(self, self);
});
}
},
/**
* Enter event listener callback
* Adds the boxShadow to the target
* @param event
* Generates the enter event listener callback
* Attaches dynamicStyles {@see palettify#options.styles.dynamic} to styleTarget
* @function
* @param {paletteObj} obj
* @name palettify#generateEnterHandler
* @return function
*/
enterHandler: function enterHandler (obj) {
generateEnterHandler: function generateEnterHandler (obj) {
return function (event) {
var target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget);
if (target) {
target.classList.add(self.options.hoverClass);
target.style.boxShadow = obj.boxShadow;
if (obj.styleTarget) {
if (typeof self.options.beforeEnterCallback === 'function') { self.options.beforeEnterCallback.call(obj.styleTarget, obj, self.options, event); }
obj.styleTarget.classList.add(self.options.activeClass);
__attachStylesToElement({
target: obj.styleTarget,
styles: self.options.styles.dynamic,
palette: obj.palette,
obj: obj
});
if (typeof self.options.afterEnterCallback === 'function') { self.options.afterEnterCallback.call(obj.styleTarget, obj, self.options, event); }
}

@@ -184,12 +423,17 @@ }

/**
* Leave Event listener callback
* Removes the Box shadow from the target
* @param event
* Generates the leave event listener callback
* Removes all dynamicStyles
* @function
* @param {paletteObj} obj
* @name palettify#generateLeaveHandler
* @return function
*/
leaveHandler: function leaveHandler (obj) {
generateLeaveHandler: function generateLeaveHandler (obj) {
return function (event) {
var target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget);
var target = obj.styleTarget;
if (target) {
target.classList.remove(self.options.hoverClass);
target.style.boxShadow = '';
if (typeof self.options.beforeLeaveCallback === 'function') { self.options.beforeLeaveCallback.call(obj.styleTarget, obj, self.options, event); }
target.classList.remove(self.options.activeClass);
__removeDynamicStylesFromElement(obj.styleTarget, self.options.styles.dynamic, self.options.styles.static, obj.palette);
if (typeof self.options.afterLeaveCallback === 'function') { self.options.afterLeaveCallback.call(obj.styleTarget, obj, self.options, event); }
}

@@ -199,3 +443,5 @@ }

/**
* Attaches Event listeners to the hoverTargets
* Attaches Event listeners to the eventTargets
* @function
* @name palettify#attachEventListeners
*/

@@ -206,11 +452,23 @@ attachEventListeners: function attachEventListeners () {

var leaveEvent = ref.leaveEvent;
self.elements.forEach(function (obj) {
obj.enterHandler = self.enterHandler(obj);
obj.leaveHandler = self.leaveHandler(obj);
obj.hoverTarget.addEventListener(enterEvent, obj.enterHandler, false);
obj.hoverTarget.addEventListener(leaveEvent, obj.leaveHandler, false);
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent;
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent;
self.data.forEach(function (obj) {
obj.enterHandler = self.generateEnterHandler(obj);
obj.leaveHandler = self.generateLeaveHandler(obj);
enterEvent.forEach(function (event) {
obj.eventTarget.addEventListener(event, obj.enterHandler, false);
});
leaveEvent.forEach(function (event) {
obj.eventTarget.addEventListener(event, obj.leaveHandler, false);
});
});
},
/**
* Detaches event listeners from hoverTargets
* Detaches event listeners from eventTargets
* @function
* @name palettify#detachEventListeners
*/

@@ -221,5 +479,13 @@ detachEventListeners: function detachEventListeners () {

var leaveEvent = ref.leaveEvent;
self.elements.forEach(function (obj) {
obj.hoverTarget.removeEventListener(enterEvent, obj.enterHandler, false);
obj.hoverTarget.removeEventListener(leaveEvent, obj.leaveHandler, false);
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent;
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent;
self.data.forEach(function (obj) {
enterEvent.forEach(function (event) {
obj.eventTarget.removeEventListener(event, obj.enterHandler, false);
});
leaveEvent.forEach(function (event) {
obj.eventTarget.removeEventListener(event, obj.leaveHandler, false);
});
});

@@ -230,2 +496,6 @@ },

* Gets fired when creating Palettify in the first place
* @constructs
* @param {DefaultOptions} opts - Options to pass to palettify
* @name palettify#init
* @return palettify
*/

@@ -235,7 +505,7 @@ init: function init (opts) {

self.options = Object.assign({}, __defaults, opts);
__mergeOptions(opts);
self.collectElements();
// Attach boxShadow's to all images
self.attachBoxShadowToCollection();
// Collect colors and attach boxShadow's to all images
self.extractColorsAndAttachStyles();
// Attach event listeners to all hovered elements.

@@ -250,6 +520,12 @@ self.attachEventListeners();

* Destroys the Palettify and cleans up after it self.
* @function
* @name palettify#destroy
* @param {Boolean} [cleanUp = true]
*/
destroy: function destroy () {
destroy: function destroy (cleanUp) {
if ( cleanUp === void 0 ) cleanUp = true;
self.detachEventListeners();
self.elements = [];
cleanUp && self.cleanUp();
self.data = [];
isInitialized = false;

@@ -260,2 +536,4 @@ },

* Destroy and then reinitialize it.
* @function
* @name palettify#reInit
*/

@@ -267,7 +545,21 @@ reInit: function reInit$1 () {

/**
* Cleans up the dom after the plugin is destroyed. Removes all styles.static
* @function
* @name palettify#cleanUp
*/
cleanUp: function cleanUp$1 () {
self.data.forEach(function (obj) {
for (var prop in self.options.styles.static) {
if (self.options.styles.static.hasOwnProperty(prop)) {
obj.styleTarget.style[prop] = '';
}
}
});
},
/**
* Set new options. Merges then with the old ones.
* Takes an options object and a reInit boolean.
* reInit defaults to True
* @param {Object} options - New options to override the old ones
* @param {Boolean} reInit - Should the plugin reInit? Defaults to true
* @param {Boolean} [reInit = true] - Should the plugin reInit? Defaults to true
* @function
* @name palettify#setOptions
*/

@@ -282,2 +574,3 @@ setOptions: function setOptions (options, reInit) {

* Is the plugin initialized
* @name palettify#isInitialized
* @return {boolean}

@@ -290,7 +583,169 @@ */

/**
* Extract the colors from image tag or Background-image inline style
* @param {HTMLElement} paletteTarget - The palette target to get the colors form
* @param {Number} colorsToExtract - Number of colors to extract
* @return {Promise} Returns promise when image is loaded with an array of RGB colors
* @private
*/
function __extractColors (paletteTarget, colorsToExtract) {
var image = __sanitizeImage(paletteTarget);
return new Promise(function (resolve) {
if (!isImageLoaded(image)) {
image.onload = function () {
resolve(new colorThief_min().getPalette(image, colorsToExtract));
};
} else {
resolve(new colorThief_min().getPalette(image, colorsToExtract));
}
})
}
/**
* Transform all colors in the palette to RGBA colors using the supplied opacities in {@see palettify#options.opacities}
* @param {Array} palette - Color palette of the current obj
* @param {Array} opacities - Array of opacities for each color
* @private
*/
function __opacifyPalette (palette, opacities) {
return palette.map(function (color, i) {
var opacity = 1;
if (Array.isArray(opacities) && opacities[i]) {
opacity = opacities[i];
}
return __getRgbaColor(color, opacity)
})
}
/**
* Returns the rgba color with applied opacity
* @param {Array} color
* @param {String | Number} opacity
* @return {string}
* @private
*/
function __getRgbaColor (color, opacity) {
var
r = color[0],
g = color[1],
b = color[2];
return ("rgba(" + r + ", " + g + ", " + b + ", " + opacity + ")")
}
/**
* Adds styles to an element.
* @param target
* @param {Object} styles
* @param palette
* @param staticCallback
* @param obj
* @private
*/
function __attachStylesToElement (ref) {
var target = ref.target;
var styles = ref.styles;
var palette = ref.palette;
var staticCallback = ref.staticCallback; if ( staticCallback === void 0 ) staticCallback = null;
var obj = ref.obj;
for (var prop in styles) {
if (styles.hasOwnProperty(prop)) {
target.style[prop] = render_1(styles[prop], palette);
}
}
staticCallback && staticCallback.call(target, obj, self.options);
}
/**
* Removes all dynamic styles from an element.
* If the staticStyle has the same prop as dynamicStyle, we set the prop to be the static style.
* @param {HTMLElement} target - The Target to remove styles from
* @param {Object} dynamicStyles - Dynamic styles to apply
* @param {Object} staticStyles - Static styles to apply
* @param {Array} palette - Array of available palettes
* @private
*/
function __removeDynamicStylesFromElement (target, dynamicStyles, staticStyles, palette) {
for (var prop in dynamicStyles) {
if (dynamicStyles.hasOwnProperty(prop) && !staticStyles.hasOwnProperty(prop)) {
target.style[prop] = '';
} else if (staticStyles.hasOwnProperty(prop)) {
target.style[prop] = render_1(staticStyles[prop], palette);
}
}
}
/**
* Merges the options and throws errors where necessary
* @param {DefaultOptions} options
* @private
*/
function __mergeOptions (options) {
self.options = index$2$1(__defaults, options, {clone: true, arrayMerge: __arrayMerge});
Object.keys(self.options).forEach(function (opt) {
if (self.options[opt] instanceof Error) {
throw self.options[opt]
}
});
}
function __getBrightness (rgb) {
return (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000
}
function __isDark (color) {
return __getBrightness(color) < 170 // 128 by default, but 170 gives better results
}
function __getInvertedColors (palette, colors) {
return palette.map(function (color) { return __isDark(color) ? colors.light : colors.dark; })
}
function __getUrl (url) {
var el = document.createElement('a');
el.href = url;
return el
}
function __isCORS (src) {
return document.location.host !== __getUrl(src).host
}
function isImageLoaded (img) {
if (!img.complete) { return false }
if (typeof img.naturalWidth !== 'undefined' && img.naturalWidth === 0) {
return false
}
// No other way of checking: assume it's ok.
return true
}
function __sanitizeImage (imgElement) {
var
cachedImg = imgElement,
isNotIMG = cachedImg.tagName !== 'IMG',
src = isNotIMG ? imgElement.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '') : imgElement.src,
isCors = __isCORS(src);
if (!cachedImg) { throw Error('Target is not an element', cachedImg) }
// Our sample is not a img tag so we try to get its background image.
if (isNotIMG || (isCors && !cachedImg.crossOrigin)) {
if (isNotIMG && !cachedImg.style.backgroundImage) { throw Error('Tag provided is not an image and does not have a background-image style attached to it.') }
cachedImg = new Image(imgElement.naturalWidth, imgElement.naturalHeight);
isCors && (cachedImg.crossOrigin = 'anonymous');
cachedImg.src = src;
}
return cachedImg
}
function __arrayMerge (destArray, sourceArray, opts) {
return sourceArray
}
return self
}
return palettify;
return createPalettify;
})));

4

dist/palettify.min.js
/*!
* palettify v0.0.0
* palettify v1.0.0
* (c) 2017 Dobromir Hristov
* Released under the MIT License.
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.palettify=t()}(this,function(){"use strict";function e(){function e(e){var r=e;if(!e)throw Error("Target is not an element",e);return"IMG"!==e.tagName&&e.style.backgroundImage&&Error("Tag provided is not an image and does not have a background-image style attached to it."),"IMG"!==e.tagName&&(r=new Image(e.offsetWidth,e.offsetHeight),r.src=e.style.backgroundImage.replace("url(","").replace(")","").replace(/"/gi,"")),(new t).getPalette(r,3)}function r(e,t){return"rgba("+e[0]+", "+e[1]+", "+e[2]+", "+t+")"}function n(e,t){var n,o=i.options,a=o.opacity,u=o.opacitySecondary,c=o.boxShadowTemplate,s=e[i.options.colorIndexToUse],f=r(s,a),h=r(s,u);n=c?c.replace("{color}",f).replace("{colorSecondary}",h):"0 2px 2px "+f+", 0 4px 4px "+f+", 0 8px 8px "+f+", 0 16px 16px "+f+", 0 32px 32px "+h+", 0 64px 64px "+h,t.colors=e,t.rgbaColor=f,t.rgbaColorSecondary=h,t.boxShadow=n}var o=!1,a={hoverTarget:Error("Please provide hoverTarget"),imageTarget:Error("Please provide ImageSrc"),attachBoxShadowTo:null,opacity:.2,opacitySecondary:.2,hoverClass:"box-shadow-attached",colorIndexToUse:0,enterEvent:"mouseenter",leaveEvent:"mouseleave",boxShadowTemplate:""},i={options:{},elements:[],collectElements:function(){var e=i.options.hoverTarget;if("string"==typeof e&&(e=document.querySelectorAll(e)),e.length)for(var t=0;t<e.length;t++){var r=e[t],n=r.querySelector(i.options.imageTarget),o={hoverTarget:r,image:n};i.elements.push(o)}},attachBoxShadowToCollection:function(){i.elements.forEach(function(t){n(e(t.image),t)})},enterHandler:function(e){return function(t){var r=t.currentTarget.querySelector(i.options.attachBoxShadowTo||i.options.imageTarget);r&&(r.classList.add(i.options.hoverClass),r.style.boxShadow=e.boxShadow)}},leaveHandler:function(e){return function(e){var t=e.currentTarget.querySelector(i.options.attachBoxShadowTo||i.options.imageTarget);t&&(t.classList.remove(i.options.hoverClass),t.style.boxShadow="")}},attachEventListeners:function(){var e=i.options,t=e.enterEvent,r=e.leaveEvent;i.elements.forEach(function(e){e.enterHandler=i.enterHandler(e),e.leaveHandler=i.leaveHandler(e),e.hoverTarget.addEventListener(t,e.enterHandler,!1),e.hoverTarget.addEventListener(r,e.leaveHandler,!1)})},detachEventListeners:function(){var e=i.options,t=e.enterEvent,r=e.leaveEvent;i.elements.forEach(function(e){e.hoverTarget.removeEventListener(t,e.enterHandler,!1),e.hoverTarget.removeEventListener(r,e.leaveHandler,!1)})},init:function(e){if(o)throw new Error("Palettify is already initialized");return i.options=Object.assign({},a,e),i.collectElements(),i.attachBoxShadowToCollection(),i.attachEventListeners(),o=!0,i},destroy:function(){i.detachEventListeners(),i.elements=[],o=!1},reInit:function(){i.destroy(),i.init(i.options)},setOptions:function(e,t){void 0===t&&(t=!0),i.options=Object.assign({},i.options,e),t&&i.reInit()},isInitialized:function(){return o}};return i}var t=function(){var e=function(){},t=function(e){this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),document.body.appendChild(this.canvas),this.width=this.canvas.width=e.width,this.height=this.canvas.height=e.height,this.context.drawImage(e,0,0,this.width,this.height)};t.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)},t.prototype.update=function(e){this.context.putImageData(e,0,0)},t.prototype.getPixelCount=function(){return this.width*this.height},t.prototype.getImageData=function(){return this.context.getImageData(0,0,this.width,this.height)},t.prototype.removeCanvas=function(){this.canvas.parentNode.removeChild(this.canvas)};var r={map:function(e,t){var r={};return t?e.map(function(e,n){return r.index=n,t.call(r,e)}):e.slice()},naturalOrder:function(e,t){return e<t?-1:e>t?1:0},sum:function(e,t){var r={};return e.reduce(t?function(e,n,o){return r.index=o,e+t.call(r,n)}:function(e,t){return e+t},0)},max:function(e,t){return Math.max.apply(null,t?r.map(e,t):e)}},n=function(){function e(e,t,r){return(e<<2*s)+(t<<s)+r}function t(e){function t(){r.sort(e),n=!0}var r=[],n=!1;return{push:function(e){r.push(e),n=!1},peek:function(e){return n||t(),void 0===e&&(e=r.length-1),r[e]},pop:function(){return n||t(),r.pop()},size:function(){return r.length},map:function(e){return r.map(e)},debug:function(){return n||t(),r}}}function n(e,t,r,n,o,a,i){var u=this;u.r1=e,u.r2=t,u.g1=r,u.g2=n,u.b1=o,u.b2=a,u.histo=i}function o(){this.vboxes=new t(function(e,t){return r.naturalOrder(e.vbox.count()*e.vbox.volume(),t.vbox.count()*t.vbox.volume())})}function a(t){var r,n,o,a,i=1<<3*s,u=new Array(i);return t.forEach(function(t){n=t[0]>>f,o=t[1]>>f,a=t[2]>>f,r=e(n,o,a),u[r]=(u[r]||0)+1}),u}function i(e,t){var r,o,a,i=1e6,u=0,c=1e6,s=0,h=1e6,l=0;return e.forEach(function(e){r=e[0]>>f,o=e[1]>>f,a=e[2]>>f,r<i?i=r:r>u&&(u=r),o<c?c=o:o>s&&(s=o),a<h?h=a:a>l&&(l=a)}),new n(i,u,c,s,h,l,t)}function u(t,n){if(n.count()){var o=n.r2-n.r1+1,a=n.g2-n.g1+1,i=n.b2-n.b1+1,u=r.max([o,a,i]);if(1==n.count())return[n.copy()];var c,s,f,h,l,p=0,v=[],g=[];if(u==o)for(c=n.r1;c<=n.r2;c++){for(h=0,s=n.g1;s<=n.g2;s++)for(f=n.b1;f<=n.b2;f++)l=e(c,s,f),h+=t[l]||0;p+=h,v[c]=p}else if(u==a)for(c=n.g1;c<=n.g2;c++){for(h=0,s=n.r1;s<=n.r2;s++)for(f=n.b1;f<=n.b2;f++)l=e(s,c,f),h+=t[l]||0;p+=h,v[c]=p}else for(c=n.b1;c<=n.b2;c++){for(h=0,s=n.r1;s<=n.r2;s++)for(f=n.g1;f<=n.g2;f++)l=e(s,f,c),h+=t[l]||0;p+=h,v[c]=p}return v.forEach(function(e,t){g[t]=p-e}),function(e){var t,r,o,a,i,u=e+"1",s=e+"2",f=0;for(c=n[u];c<=n[s];c++)if(v[c]>p/2){for(o=n.copy(),a=n.copy(),t=c-n[u],r=n[s]-c,i=t<=r?Math.min(n[s]-1,~~(c+r/2)):Math.max(n[u],~~(c-1-t/2));!v[i];)i++;for(f=g[i];!f&&v[i-1];)f=g[--i];return o[s]=i,a[u]=o[s]+1,[o,a]}}(u==o?"r":u==a?"g":"b")}}function c(e,n){function c(e,t){for(var r,n=1,o=0;o<h;)if(r=e.pop(),r.count()){var a=u(s,r),i=a[0],c=a[1];if(!i)return;if(e.push(i),c&&(e.push(c),n++),n>=t)return;if(o++>h)return}else e.push(r),o++}if(!e.length||n<2||n>256)return!1;var s=a(e),f=0;s.forEach(function(){f++});var p=i(e,s),v=new t(function(e,t){return r.naturalOrder(e.count(),t.count())});v.push(p),c(v,l*n);for(var g=new t(function(e,t){return r.naturalOrder(e.count()*e.volume(),t.count()*t.volume())});v.size();)g.push(v.pop());c(g,n-g.size());for(var d=new o;g.size();)d.push(g.pop());return d}var s=5,f=8-s,h=1e3,l=.75;return n.prototype={volume:function(e){var t=this;return t._volume&&!e||(t._volume=(t.r2-t.r1+1)*(t.g2-t.g1+1)*(t.b2-t.b1+1)),t._volume},count:function(t){var r=this,n=r.histo;if(!r._count_set||t){var o,a,i,u,c=0;for(a=r.r1;a<=r.r2;a++)for(i=r.g1;i<=r.g2;i++)for(u=r.b1;u<=r.b2;u++)o=e(a,i,u),c+=n[o]||0;r._count=c,r._count_set=!0}return r._count},copy:function(){var e=this;return new n(e.r1,e.r2,e.g1,e.g2,e.b1,e.b2,e.histo)},avg:function(t){var r=this,n=r.histo;if(!r._avg||t){var o,a,i,u,c,f=0,h=1<<8-s,l=0,p=0,v=0;for(a=r.r1;a<=r.r2;a++)for(i=r.g1;i<=r.g2;i++)for(u=r.b1;u<=r.b2;u++)c=e(a,i,u),o=n[c]||0,f+=o,l+=o*(a+.5)*h,p+=o*(i+.5)*h,v+=o*(u+.5)*h;r._avg=f?[~~(l/f),~~(p/f),~~(v/f)]:[~~(h*(r.r1+r.r2+1)/2),~~(h*(r.g1+r.g2+1)/2),~~(h*(r.b1+r.b2+1)/2)]}return r._avg},contains:function(e){var t=this,r=e[0]>>f,n=e[1]>>f,o=e[2]>>f;return r>=t.r1&&r<=t.r2&&n>=t.g1&&n<=t.g2&&o>=t.b1&&o<=t.b2}},o.prototype={push:function(e){this.vboxes.push({vbox:e,color:e.avg()})},palette:function(){return this.vboxes.map(function(e){return e.color})},size:function(){return this.vboxes.size()},map:function(e){for(var t=this.vboxes,r=0;r<t.size();r++)if(t.peek(r).vbox.contains(e))return t.peek(r).color;return this.nearest(e)},nearest:function(e){for(var t,r,n,o=this.vboxes,a=0;a<o.size();a++)((r=Math.sqrt(Math.pow(e[0]-o.peek(a).color[0],2)+Math.pow(e[1]-o.peek(a).color[1],2)+Math.pow(e[2]-o.peek(a).color[2],2)))<t||void 0===t)&&(t=r,n=o.peek(a).color);return n},forcebw:function(){var e=this.vboxes;e.sort(function(e,t){return r.naturalOrder(r.sum(e.color),r.sum(t.color))});var t=e[0].color;t[0]<5&&t[1]<5&&t[2]<5&&(e[0].color=[0,0,0]);var n=e.length-1,o=e[n].color;o[0]>251&&o[1]>251&&o[2]>251&&(e[n].color=[255,255,255])}},{quantize:c}}();return e.prototype.getColor=function(e,t){return this.getPalette(e,5,t)[0]},e.prototype.getPalette=function(e,r,o){(void 0===r||r<2||r>256)&&(r=10),(void 0===o||o<1)&&(o=10);for(var a,i,u,c,s=new t(e),f=s.getImageData(),h=f.data,l=s.getPixelCount(),p=[],v=0;v<l;v+=o)a=4*v,i=h[a+0],u=h[a+1],c=h[a+2],h[a+3]>=125&&(i>250&&u>250&&c>250||p.push([i,u,c]));var g=n.quantize(p,r),d=g?g.palette():null;return s.removeCanvas(),d},e.prototype.getColorFromUrl=function(e,t,r){sourceImage=document.createElement("img");var n=this;sourceImage.addEventListener("load",function(){t(n.getPalette(sourceImage,5,r)[0],e)}),sourceImage.src=e},e.prototype.getImageData=function(e,t){xhr=new XMLHttpRequest,xhr.open("GET",e,!0),xhr.responseType="arraybuffer",xhr.onload=function(e){if(200==this.status){uInt8Array=new Uint8Array(this.response),r=uInt8Array.length,binaryString=new Array(r);for(var r=0;r<uInt8Array.length;r++)binaryString[r]=String.fromCharCode(uInt8Array[r]);data=binaryString.join(""),base64=window.btoa(data),t("data:image/png;base64,"+base64)}},xhr.send()},e.prototype.getColorAsync=function(e,t,r){var n=this;this.getImageData(e,function(e){sourceImage=document.createElement("img"),sourceImage.addEventListener("load",function(){t(n.getPalette(sourceImage,5,r)[0],this)}),sourceImage.src=e})},e}();return e});
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.palettify=e()}(this,function(){"use strict";function t(t){switch(typeof t){case"string":case"number":case"boolean":return t;case"object":try{return null===t?"":JSON.stringify(t)}catch(t){return"{...}"}default:return""}}function e(t,n,r){if(void 0===r&&(r=0),"object"!=typeof t||null===t||void 0===t)return"";var o=n[r],a=t[o];return r===n.length-1?a:e(a,n,++r)}function n(t,n){return e(n,t.split("."))}function r(e,r,o){return void 0===r&&(r={}),void 0===o&&(o=n),"string"!=typeof e?e:e.replace(v,function(e,a){try{return t(o(a,r))}catch(e){return t(n(a,r))}})}function o(t){return!!t&&"object"==typeof t}function a(t){var e=Object.prototype.toString.call(t);return"[object RegExp]"!==e&&"[object Date]"!==e}function i(t){return Array.isArray(t)?[]:{}}function s(t,e){return e&&!0===e.clone&&y(t)?l(i(t),t,e):t}function c(t,e,n){var r=t.slice();return e.forEach(function(e,o){void 0===r[o]?r[o]=s(e,n):y(e)?r[o]=l(t[o],e,n):-1===t.indexOf(e)&&r.push(s(e,n))}),r}function u(t,e,n){var r={};return y(t)&&Object.keys(t).forEach(function(e){r[e]=s(t[e],n)}),Object.keys(e).forEach(function(o){y(e[o])&&t[o]?r[o]=l(t[o],e[o],n):r[o]=s(e[o],n)}),r}function l(t,e,n){var r=Array.isArray(e),o=n||{arrayMerge:c},a=o.arrayMerge||c;return r?Array.isArray(t)?a(t,e,n):s(e,n):u(t,e,n)}function f(){function t(t,e){var n=v(t);return new Promise(function(t){f(n)?t((new p).getPalette(n,e)):n.onload=function(){t((new p).getPalette(n,e))}})}function e(t,e){return t.map(function(t,r){var o=1;return Array.isArray(e)&&e[r]&&(o=e[r]),n(t,o)})}function n(t,e){return"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function r(t){var e=t.target,n=t.styles,r=t.palette,o=t.staticCallback;void 0===o&&(o=null);var a=t.obj;for(var i in n)n.hasOwnProperty(i)&&(e.style[i]=h(n[i],r));o&&o.call(e,a,E.options)}function o(t,e,n,r){for(var o in e)e.hasOwnProperty(o)&&!n.hasOwnProperty(o)?t.style[o]="":n.hasOwnProperty(o)&&(t.style[o]=h(n[o],r))}function a(t){E.options=d(m,t,{clone:!0,arrayMerge:g}),Object.keys(E.options).forEach(function(t){if(E.options[t]instanceof Error)throw E.options[t]})}function i(t){return(299*t[0]+587*t[1]+114*t[2])/1e3}function s(t){return i(t)<170}function c(t,e){return t.map(function(t){return s(t)?e.light:e.dark})}function u(t){var e=document.createElement("a");return e.href=t,e}function l(t){return document.location.host!==u(t).host}function f(t){return!!t.complete&&(void 0===t.naturalWidth||0!==t.naturalWidth)}function v(t){var e=t,n="IMG"!==e.tagName,r=n?t.style.backgroundImage.replace("url(","").replace(")","").replace(/"/gi,""):t.src,o=l(r);if(!e)throw Error("Target is not an element",e);if(n||o&&!e.crossOrigin){if(n&&!e.style.backgroundImage)throw Error("Tag provided is not an image and does not have a background-image style attached to it.");e=new Image(t.naturalWidth,t.naturalHeight),o&&(e.crossOrigin="anonymous"),e.src=r}return e}function g(t,e,n){return e}var y=!1,b=null,m={selector:Error("Please provide \u0430 selector in your options."),eventTarget:Error("Please provide an eventTarget as a parent for your image in the options."),image:Error("Please provide an image to sample."),styleTarget:null,contrastColors:{light:"#fff",dark:"#000"},activeClass:"palettify--active",readyClass:"palettify--ready",colorsToExtract:3,enterEvent:"mouseenter",leaveEvent:"mouseleave",styles:{opacities:[.5,.5,.5],static:{},dynamic:{}},staticCallback:null,beforeEnterCallback:null,afterEnterCallback:null,beforeLeaveCallback:null,afterLeaveCallback:null,onReadyCallback:null},E={options:{},data:[],collectElements:function(){var t="";if(!(b="string"==typeof E.options.selector?document.querySelector(E.options.selector):E.options.selector))throw new Error("Selector "+E.options.selector+" does not exist");t=b.querySelectorAll(E.options.eventTarget),[].slice.call(t,0).forEach(function(t){var e=t.querySelector(E.options.image),n=!0===E.options.styleTarget?t:t.querySelector(E.options.styleTarget||E.options.image),r={eventTarget:t,styleTarget:n,image:e,palette:{original:[],rgb:[],rgba:[]}};E.data.push(r)})},extractColorsAndAttachStyles:function(n){void 0===n&&(n=!1);var o=[];E.data.forEach(function(n,a){o[a]=t(n.image,E.options.colorsToExtract).then(function(t){n.palette.original=t,n.palette.rgb=e(n.palette.original,[]),n.palette.rgba=e(n.palette.original,E.options.styles.opacities),n.palette.contrastColors=c(n.palette.original,E.options.contrastColors),r({target:n.styleTarget,styles:E.options.styles.static,palette:n.palette,staticCallback:E.options.staticCallback,obj:n})})}),n||Promise.all(o).then(function(t){b.classList.add(E.options.readyClass),"function"==typeof E.options.onReadyCallback&&E.options.onReadyCallback.call(E,E)})},generateEnterHandler:function(t){return function(e){t.styleTarget&&("function"==typeof E.options.beforeEnterCallback&&E.options.beforeEnterCallback.call(t.styleTarget,t,E.options,e),t.styleTarget.classList.add(E.options.activeClass),r({target:t.styleTarget,styles:E.options.styles.dynamic,palette:t.palette,obj:t}),"function"==typeof E.options.afterEnterCallback&&E.options.afterEnterCallback.call(t.styleTarget,t,E.options,e))}},generateLeaveHandler:function(t){return function(e){var n=t.styleTarget;n&&("function"==typeof E.options.beforeLeaveCallback&&E.options.beforeLeaveCallback.call(t.styleTarget,t,E.options,e),n.classList.remove(E.options.activeClass),o(t.styleTarget,E.options.styles.dynamic,E.options.styles.static,t.palette),"function"==typeof E.options.afterLeaveCallback&&E.options.afterLeaveCallback.call(t.styleTarget,t,E.options,e))}},attachEventListeners:function(){var t=E.options,e=t.enterEvent,n=t.leaveEvent;e=Array.isArray(e)?e:[e],n=Array.isArray(n)?n:[n],E.data.forEach(function(t){t.enterHandler=E.generateEnterHandler(t),t.leaveHandler=E.generateLeaveHandler(t),e.forEach(function(e){t.eventTarget.addEventListener(e,t.enterHandler,!1)}),n.forEach(function(e){t.eventTarget.addEventListener(e,t.leaveHandler,!1)})})},detachEventListeners:function(){var t=E.options,e=t.enterEvent,n=t.leaveEvent;e=Array.isArray(e)?e:[e],n=Array.isArray(n)?n:[n],E.data.forEach(function(t){e.forEach(function(e){t.eventTarget.removeEventListener(e,t.enterHandler,!1)}),n.forEach(function(e){t.eventTarget.removeEventListener(e,t.leaveHandler,!1)})})},init:function(t){if(y)throw new Error("Palettify is already initialized");return a(t),E.collectElements(),E.extractColorsAndAttachStyles(),E.attachEventListeners(),y=!0,E},destroy:function(t){void 0===t&&(t=!0),E.detachEventListeners(),t&&E.cleanUp(),E.data=[],y=!1},reInit:function(){E.destroy(),E.init(E.options)},cleanUp:function(){E.data.forEach(function(t){for(var e in E.options.styles.static)E.options.styles.static.hasOwnProperty(e)&&(t.styleTarget.style[e]="")})},setOptions:function(t,e){void 0===e&&(e=!0),E.options=Object.assign({},E.options,t),e&&E.reInit()},isInitialized:function(){return y}};return E}var p=function(){var t=function(){},e=function(t){this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),document.body.appendChild(this.canvas),this.width=this.canvas.width=t.width,this.height=this.canvas.height=t.height,this.context.drawImage(t,0,0,this.width,this.height)};e.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)},e.prototype.update=function(t){this.context.putImageData(t,0,0)},e.prototype.getPixelCount=function(){return this.width*this.height},e.prototype.getImageData=function(){return this.context.getImageData(0,0,this.width,this.height)},e.prototype.removeCanvas=function(){this.canvas.parentNode.removeChild(this.canvas)};var n={map:function(t,e){var n={};return e?t.map(function(t,r){return n.index=r,e.call(n,t)}):t.slice()},naturalOrder:function(t,e){return t<e?-1:t>e?1:0},sum:function(t,e){var n={};return t.reduce(e?function(t,r,o){return n.index=o,t+e.call(n,r)}:function(t,e){return t+e},0)},max:function(t,e){return Math.max.apply(null,e?n.map(t,e):t)}},r=function(){function t(t,e,n){return(t<<2*u)+(e<<u)+n}function e(t){function e(){n.sort(t),r=!0}var n=[],r=!1;return{push:function(t){n.push(t),r=!1},peek:function(t){return r||e(),void 0===t&&(t=n.length-1),n[t]},pop:function(){return r||e(),n.pop()},size:function(){return n.length},map:function(t){return n.map(t)},debug:function(){return r||e(),n}}}function r(t,e,n,r,o,a,i){var s=this;s.r1=t,s.r2=e,s.g1=n,s.g2=r,s.b1=o,s.b2=a,s.histo=i}function o(){this.vboxes=new e(function(t,e){return n.naturalOrder(t.vbox.count()*t.vbox.volume(),e.vbox.count()*e.vbox.volume())})}function a(e){var n,r,o,a,i=1<<3*u,s=new Array(i);return e.forEach(function(e){r=e[0]>>l,o=e[1]>>l,a=e[2]>>l,n=t(r,o,a),s[n]=(s[n]||0)+1}),s}function i(t,e){var n,o,a,i=1e6,s=0,c=1e6,u=0,f=1e6,p=0;return t.forEach(function(t){n=t[0]>>l,o=t[1]>>l,a=t[2]>>l,n<i?i=n:n>s&&(s=n),o<c?c=o:o>u&&(u=o),a<f?f=a:a>p&&(p=a)}),new r(i,s,c,u,f,p,e)}function s(e,r){if(r.count()){var o=r.r2-r.r1+1,a=r.g2-r.g1+1,i=r.b2-r.b1+1,s=n.max([o,a,i]);if(1==r.count())return[r.copy()];var c,u,l,f,p,v=0,g=[],h=[];if(s==o)for(c=r.r1;c<=r.r2;c++){for(f=0,u=r.g1;u<=r.g2;u++)for(l=r.b1;l<=r.b2;l++)p=t(c,u,l),f+=e[p]||0;v+=f,g[c]=v}else if(s==a)for(c=r.g1;c<=r.g2;c++){for(f=0,u=r.r1;u<=r.r2;u++)for(l=r.b1;l<=r.b2;l++)p=t(u,c,l),f+=e[p]||0;v+=f,g[c]=v}else for(c=r.b1;c<=r.b2;c++){for(f=0,u=r.r1;u<=r.r2;u++)for(l=r.g1;l<=r.g2;l++)p=t(u,l,c),f+=e[p]||0;v+=f,g[c]=v}return g.forEach(function(t,e){h[e]=v-t}),function(t){var e,n,o,a,i,s=t+"1",u=t+"2",l=0;for(c=r[s];c<=r[u];c++)if(g[c]>v/2){for(o=r.copy(),a=r.copy(),e=c-r[s],n=r[u]-c,i=e<=n?Math.min(r[u]-1,~~(c+n/2)):Math.max(r[s],~~(c-1-e/2));!g[i];)i++;for(l=h[i];!l&&g[i-1];)l=h[--i];return o[u]=i,a[s]=o[u]+1,[o,a]}}(s==o?"r":s==a?"g":"b")}}function c(t,r){function c(t,e){for(var n,r=1,o=0;o<f;)if(n=t.pop(),n.count()){var a=s(u,n),i=a[0],c=a[1];if(!i)return;if(t.push(i),c&&(t.push(c),r++),r>=e)return;if(o++>f)return}else t.push(n),o++}if(!t.length||r<2||r>256)return!1;var u=a(t),l=0;u.forEach(function(){l++});var v=i(t,u),g=new e(function(t,e){return n.naturalOrder(t.count(),e.count())});g.push(v),c(g,p*r);for(var h=new e(function(t,e){return n.naturalOrder(t.count()*t.volume(),e.count()*e.volume())});g.size();)h.push(g.pop());c(h,r-h.size());for(var y=new o;h.size();)y.push(h.pop());return y}var u=5,l=8-u,f=1e3,p=.75;return r.prototype={volume:function(t){var e=this;return e._volume&&!t||(e._volume=(e.r2-e.r1+1)*(e.g2-e.g1+1)*(e.b2-e.b1+1)),e._volume},count:function(e){var n=this,r=n.histo;if(!n._count_set||e){var o,a,i,s,c=0;for(a=n.r1;a<=n.r2;a++)for(i=n.g1;i<=n.g2;i++)for(s=n.b1;s<=n.b2;s++)o=t(a,i,s),c+=r[o]||0;n._count=c,n._count_set=!0}return n._count},copy:function(){var t=this;return new r(t.r1,t.r2,t.g1,t.g2,t.b1,t.b2,t.histo)},avg:function(e){var n=this,r=n.histo;if(!n._avg||e){var o,a,i,s,c,l=0,f=1<<8-u,p=0,v=0,g=0;for(a=n.r1;a<=n.r2;a++)for(i=n.g1;i<=n.g2;i++)for(s=n.b1;s<=n.b2;s++)c=t(a,i,s),o=r[c]||0,l+=o,p+=o*(a+.5)*f,v+=o*(i+.5)*f,g+=o*(s+.5)*f;n._avg=l?[~~(p/l),~~(v/l),~~(g/l)]:[~~(f*(n.r1+n.r2+1)/2),~~(f*(n.g1+n.g2+1)/2),~~(f*(n.b1+n.b2+1)/2)]}return n._avg},contains:function(t){var e=this,n=t[0]>>l,r=t[1]>>l,o=t[2]>>l;return n>=e.r1&&n<=e.r2&&r>=e.g1&&r<=e.g2&&o>=e.b1&&o<=e.b2}},o.prototype={push:function(t){this.vboxes.push({vbox:t,color:t.avg()})},palette:function(){return this.vboxes.map(function(t){return t.color})},size:function(){return this.vboxes.size()},map:function(t){for(var e=this.vboxes,n=0;n<e.size();n++)if(e.peek(n).vbox.contains(t))return e.peek(n).color;return this.nearest(t)},nearest:function(t){for(var e,n,r,o=this.vboxes,a=0;a<o.size();a++)((n=Math.sqrt(Math.pow(t[0]-o.peek(a).color[0],2)+Math.pow(t[1]-o.peek(a).color[1],2)+Math.pow(t[2]-o.peek(a).color[2],2)))<e||void 0===e)&&(e=n,r=o.peek(a).color);return r},forcebw:function(){var t=this.vboxes;t.sort(function(t,e){return n.naturalOrder(n.sum(t.color),n.sum(e.color))});var e=t[0].color;e[0]<5&&e[1]<5&&e[2]<5&&(t[0].color=[0,0,0]);var r=t.length-1,o=t[r].color;o[0]>251&&o[1]>251&&o[2]>251&&(t[r].color=[255,255,255])}},{quantize:c}}();return t.prototype.getColor=function(t,e){return this.getPalette(t,5,e)[0]},t.prototype.getPalette=function(t,n,o){(void 0===n||n<2||n>256)&&(n=10),(void 0===o||o<1)&&(o=10);for(var a,i,s,c,u=new e(t),l=u.getImageData(),f=l.data,p=u.getPixelCount(),v=[],g=0;g<p;g+=o)a=4*g,i=f[a+0],s=f[a+1],c=f[a+2],f[a+3]>=125&&(i>250&&s>250&&c>250||v.push([i,s,c]));var h=r.quantize(v,n),y=h?h.palette():null;return u.removeCanvas(),y},t.prototype.getColorFromUrl=function(t,e,n){sourceImage=document.createElement("img");var r=this;sourceImage.addEventListener("load",function(){e(r.getPalette(sourceImage,5,n)[0],t)}),sourceImage.src=t},t.prototype.getImageData=function(t,e){xhr=new XMLHttpRequest,xhr.open("GET",t,!0),xhr.responseType="arraybuffer",xhr.onload=function(t){if(200==this.status){uInt8Array=new Uint8Array(this.response),n=uInt8Array.length,binaryString=new Array(n);for(var n=0;n<uInt8Array.length;n++)binaryString[n]=String.fromCharCode(uInt8Array[n]);data=binaryString.join(""),base64=window.btoa(data),e("data:image/png;base64,"+base64)}},xhr.send()},t.prototype.getColorAsync=function(t,e,n){var r=this;this.getImageData(t,function(t){sourceImage=document.createElement("img"),sourceImage.addEventListener("load",function(){e(r.getPalette(sourceImage,5,n)[0],this)}),sourceImage.src=t})},t}(),v=/\{\{\s*(.*?)\s*\}\}/g,g=r,h=g,y=function(t){return o(t)&&a(t)};l.all=function(t,e){if(!Array.isArray(t)||t.length<2)throw new Error("first argument should be an array with at least two elements");return t.reduce(function(t,n){return l(t,n,e)})};var d=l;return f});

@@ -6,2 +6,3 @@ ## Explanation of Build Files

- ES Module: palettify.esm.js
- UMD: palettify.standalone.js

@@ -15,1 +16,3 @@ ### Terms

- **[ES Module](http://exploringjs.com/es6/ch_modules.html)**: ES module builds are intended for use with modern bundlers like [webpack 2](https://webpack.js.org) or [rollup](http://rollupjs.org/). The default file for these bundlers (`pkg.module`) is the Runtime only ES Module build (`palettify.esm.js`).
- **Standalone**: UMD packaged module that can be used directly in the browser but requires the user to have [Color Thief](http://lokeshdhakar.com/projects/color-thief/) library added before Palettify.
{
"name": "palettify",
"description": "Configurable JavaScript plugin to extract image primary colors and apply cool effects to it.",
"version": "0.0.0",
"version": "1.0.0",
"author": {

@@ -13,2 +13,3 @@ "name": "Dobromir Hristov"

"@mariotacke/color-thief": "^3.0.1",
"autoprefixer": "^7.1.1",
"babel-core": "^6.22.1",

@@ -25,2 +26,3 @@ "babel-eslint": "^7.1.0",

"conventional-github-releaser": "^1.1.3",
"cz-conventional-changelog": "^2.0.0",
"eslint": "^3.14.1",

@@ -30,2 +32,4 @@ "eslint-config-standard": "^10.2.1",

"eslint-plugin-import": "^2.3.0",
"foundation-sites": "^6.4.1",
"fs-extra": "^3.0.1",
"gitbook-cli": "^2.3.0",

@@ -45,5 +49,8 @@ "html-webpack-plugin": "^2.28.0",

"karma-webpack": "^2.0.2",
"micromustache": "^5.2.0",
"mocha": "^3.2.0",
"mocha-loader": "^1.1.1",
"node-sass": "^4.5.3",
"phantomjs-prebuilt": "^2.1.14",
"postcss": "^6.0.6",
"power-assert": "^1.4.2",

@@ -58,5 +65,7 @@ "rollup": "^0.36.4",

"rollup-plugin-replace": "^1.1.1",
"rollup-plugin-sass": "^0.5.3",
"rollup-watch": "^4.0.0",
"sass-loader": "^6.0.6",
"uglify-js": "^2.7.5",
"webpack": "^2.2.0",
"webpack": "^2.6.1",
"webpack-dev-server": "^2.4.5",

@@ -103,4 +112,11 @@ "yargs-parser": "^7.0.0"

"dependencies": {
"config-chain": "^1.1.11"
"config-chain": "^1.1.11",
"deepmerge": "^1.4.4",
"jsonfile": "^3.0.0"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

@@ -6,4 +6,5 @@ # Palettify

[![npm](https://img.shields.io/npm/v/palettify.svg)](https://www.npmjs.com/package/palettify)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)
Configurable JavaScript plugin to extract image primary color and apply beautiful effects to it.
Configurable JavaScript plugin to extract image primary colors and apply cool effects to it.

@@ -10,0 +11,0 @@

import ColorThief from '@mariotacke/color-thief'
import { render } from 'micromustache'
import merge from 'deepmerge'
/**
* palettify
* @param {Object} opts
* @param {String | NodeList} opts.imageTarget
* @param {String | NodeList} opts.hoverTarget
* @param {String} opts.attachBoxShadowTo
* @param {String | Number} opts.opacity
* @param {String} opts.opacitySecondary
* @param {String} opts.colorIndexToUse
* @param {String} opts.boxShadowTemplate - Provide a boxShadow template to apply. '0 2px 2px {color}, 3px 3px {colorSecondary}'
* Creates a palettify instance
* @module palettify
* @return palettify
*/
function palettify () {
/**
* Extract the colors from image tag or Background-image inline style
* @param {HTMLElement} paletteTarget - The palette target to get the colors form
* @return {Array} Returns an object with RGB colors
* @private
*/
function __extractColors (paletteTarget) {
let image = paletteTarget
if (!paletteTarget) throw Error('Target is not an element', paletteTarget)
// Our sample is not a img tag so we try to get its background image.
if (paletteTarget.tagName !== 'IMG' && paletteTarget.style.backgroundImage) Error('Tag provided is not an image and does not have a background-image style attached to it.')
// Its not an IMG tag so we try to crate one under the hood.
if (paletteTarget.tagName !== 'IMG') {
image = new Image(paletteTarget.offsetWidth, paletteTarget.offsetHeight)
image.src = paletteTarget.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '')
}
return new ColorThief().getPalette(image, 3)
}
function createPalettify () {
let
isInitialized = false,
__selector = null
/**
* Returns the rgba color from current color with applied opacity
* @param {Array} color
* @param {String | Number} opacity
* @return {string}
* @private
* @typedef {Object} paletteObj
* @property {HTMLElement} eventTarget
* @property {HTMLElement} styleTarget
* @property {HTMLElement} image
* @property {PaletteObject} palette
* @property {Function} enterHandler
* @property {Function} leaveHandler
*/
function __getRgbaColor (color, opacity) {
const
r = color[0],
g = color[1],
b = color[2]
return `rgba(${r}, ${g}, ${b}, ${opacity})`
}
/**
* Adds boxShadow to an element.
* Required the color and the element to attach to
* @param {Array} colors - Array of RGB Colors
* @param {Object} obj - Object that holds the hoverTarget and image
* @param {HTMLElement} obj.hoverTarget - Reference to the hoverTarget dom element
* @param {HTMLElement} obj.image - Reference to the image dom element
* @param {Array} obj.colors - Array of RGB Colors
* @param {String} obj.rgbaColor - rgba transformed color with opacity in mind.
* @param {String} obj.rgbaColorSecondary - rgba transformed color with secondary opacity in mind.
* @private
* @typedef {Object} PaletteObject
* @property {Array} original
* @property {Array} rgb
* @property {Array} rgba
*/
function __addBoxShadowToElementData (colors, obj) {
const
{opacity, opacitySecondary, boxShadowTemplate} = self.options,
color = colors[self.options.colorIndexToUse],
rgbaColor = __getRgbaColor(color, opacity),
rgbaColorSecondary = __getRgbaColor(color, opacitySecondary)
let boxShadow
if (!boxShadowTemplate) {
boxShadow = `0 2px 2px ${rgbaColor}, 0 4px 4px ${rgbaColor}, 0 8px 8px ${rgbaColor}, 0 16px 16px ${rgbaColor}, 0 32px 32px ${rgbaColorSecondary}, 0 64px 64px ${rgbaColorSecondary}`
} else {
boxShadow = boxShadowTemplate.replace('{color}', rgbaColor).replace('{colorSecondary}', rgbaColorSecondary)
}
obj.colors = colors
obj.rgbaColor = rgbaColor
obj.rgbaColorSecondary = rgbaColorSecondary
obj.boxShadow = boxShadow
}
let isInitialized = false
const
/**
* Default options to extend
* @typedef {Object} DefaultOptions
* @property {String|HTMLElement} selector - The element that holds all your images and eventTargets.
* @property {String} eventTarget - The event target to attach event listeners to
* @property {String} image - The image to sample
* @property {String} styleTarget - The element to apply styling to. Defaults to image
* @property {Object} contrastColors - Light and Dark colors, based on brightness of each color in the palette
* @property {String} contrastColors.light - Light inverted color
* @property {String} contrastColors.dark - Dark inverted color
* @property {String} activeClass - CSS class to apply on each enterEvent
* @property {String} readyClass - CSS class to apply when palettify is ready
* @property {Number} colorsToExtract - Colors to extract
* @property {String | Array} enterEvent - Event or Array of events to apply listeners to for each enterCallback
* @property {string | Array} leaveEvent - Event or Array of events to apply listeners to for each leaveCallback
* @property {Object} styles - Collection of static and dynamic styles
* @property {Array<number>} styles.opacities - Array of opacities
* @property {Object} styles.static - Object containing valid css styles to apply to styleTarget on ready
* @property {Object} styles.dynamic - Object containing valid css styles to apply to styleTarget on each enterEvent
* @property {Function} beforeEnterCallback - Callback called before the enter event
* @property {Function} afterEnterCallback - Callback called after the enter event
* @property {Function} beforeLeaveCallback - Callback called before the leave event
* @property {Function} afterLeaveCallback - Callback called after the enter event
* @property {Function} onReadyCallback - Callback called after palettify is ready.
*/
__defaults = {
hoverTarget: Error('Please provide hoverTarget'),
imageTarget: Error('Please provide ImageSrc'),
attachBoxShadowTo: null,
opacity: 0.2,
opacitySecondary: 0.2,
hoverClass: 'box-shadow-attached',
colorIndexToUse: 0,
selector: Error('Please provide а selector in your options.'),
eventTarget: Error('Please provide an eventTarget as a parent for your image in the options.'),
image: Error('Please provide an image to sample.'),
styleTarget: null,
contrastColors: {
light: '#fff',
dark: '#000'
},
activeClass: 'palettify--active',
readyClass: 'palettify--ready',
colorsToExtract: 3,
enterEvent: 'mouseenter',
leaveEvent: 'mouseleave',
boxShadowTemplate: ''
styles: {
opacities: [0.5, 0.5, 0.5],
static: {},
dynamic: {}
},
staticCallback: null,
beforeEnterCallback: null,
afterEnterCallback: null,
beforeLeaveCallback: null,
afterLeaveCallback: null,
onReadyCallback: null
},
/**
* @typedef {Object} palettify
* @type Object
* @property {DefaultOptions} options
* @property {Array} data
* @property {function} collectElements
* @property {function} extractColorsAndAttachStyles
* @property {function} generateEnterHandler
* @property {function} generateLeaveHandler
* @property {function} attachEventListeners
* @property {function} detachEventListeners
* @property {function} init
* @property {function} destroy
* @property {function} reInit
* @property {function} cleanUp
* @property {function} setOptions
* @property {function} isInitialized
*/
self = {
/**
* Palettify options
* @name palettify#options
* @type DefaultOptions
*/
options: {},
elements: [],
/**
* Gather all needed elements and push into an the `elements` array
* Holds a collection of {@link paletteObj} objects.
* @type Array<paletteObj>
* @name palettify#data
*/
data: [],
/**
* Gather all needed elements and push into an the {@see palettify#data}
* @type function
* @name palettify#collectElements
*/
collectElements () {
let hoverTargetsCollection = self.options.hoverTarget
if (typeof hoverTargetsCollection === 'string') {
hoverTargetsCollection = document.querySelectorAll(hoverTargetsCollection)
}
if (!hoverTargetsCollection.length) return
for (let i = 0; i < hoverTargetsCollection.length; i++) {
let
eventTargetsCollection = ''
__selector = typeof self.options.selector === 'string' ? document.querySelector(self.options.selector) : self.options.selector
if (!__selector) { throw new Error('Selector ' + self.options.selector + ' does not exist') }
eventTargetsCollection = __selector.querySelectorAll(self.options.eventTarget);
[].slice.call(eventTargetsCollection, 0).forEach((eventTarget) => {
const
element = hoverTargetsCollection[i],
image = element.querySelector(self.options.imageTarget),
image = eventTarget.querySelector(self.options.image),
styleTarget = self.options.styleTarget === true ? eventTarget : eventTarget.querySelector(self.options.styleTarget || self.options.image),
// Create the main object it self.
obj = {
hoverTarget: element,
image
eventTarget,
styleTarget,
image,
palette: {
original: [],
rgb: [],
rgba: []
}
}
self.elements.push(obj)
}
self.data.push(obj)
})
},
/**
* Attaches boxShadow to the collection of Images
* Extracts colors and attaches static styles to each styleTarget {@see palettify#options.styleTarget}
* Adds ready class and calls onReadyCallback when done
* @function
* @name palettify#extractColorsAndAttachStyles
*/
attachBoxShadowToCollection () {
self.elements.forEach(obj => {
const color = __extractColors(obj.image)
__addBoxShadowToElementData(color, obj)
extractColorsAndAttachStyles (skipCallbacks = false) {
let promises = []
self.data.forEach((obj, index) => {
promises[index] = __extractColors(obj.image, self.options.colorsToExtract).then(colors => {
obj.palette.original = colors
obj.palette.rgb = __opacifyPalette(obj.palette.original, [])
obj.palette.rgba = __opacifyPalette(obj.palette.original, self.options.styles.opacities)
obj.palette.contrastColors = __getInvertedColors(obj.palette.original, self.options.contrastColors)
__attachStylesToElement({
target: obj.styleTarget,
styles: self.options.styles.static,
palette: obj.palette,
staticCallback: self.options.staticCallback,
obj
})
})
})
if (!skipCallbacks) {
Promise.all(promises).then(values => {
__selector.classList.add(self.options.readyClass)
typeof self.options.onReadyCallback === 'function' && self.options.onReadyCallback.call(self, self)
})
}
},
/**
* Enter event listener callback
* Adds the boxShadow to the target
* @param event
* Generates the enter event listener callback
* Attaches dynamicStyles {@see palettify#options.styles.dynamic} to styleTarget
* @function
* @param {paletteObj} obj
* @name palettify#generateEnterHandler
* @return function
*/
enterHandler (obj) {
generateEnterHandler (obj) {
return (event) => {
const target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget)
if (target) {
target.classList.add(self.options.hoverClass)
target.style.boxShadow = obj.boxShadow
if (obj.styleTarget) {
if (typeof self.options.beforeEnterCallback === 'function') self.options.beforeEnterCallback.call(obj.styleTarget, obj, self.options, event)
obj.styleTarget.classList.add(self.options.activeClass)
__attachStylesToElement({
target: obj.styleTarget,
styles: self.options.styles.dynamic,
palette: obj.palette,
obj
})
if (typeof self.options.afterEnterCallback === 'function') self.options.afterEnterCallback.call(obj.styleTarget, obj, self.options, event)
}

@@ -140,12 +197,17 @@ }

/**
* Leave Event listener callback
* Removes the Box shadow from the target
* @param event
* Generates the leave event listener callback
* Removes all dynamicStyles
* @function
* @param {paletteObj} obj
* @name palettify#generateLeaveHandler
* @return function
*/
leaveHandler (obj) {
generateLeaveHandler (obj) {
return (event) => {
const target = event.currentTarget.querySelector(self.options.attachBoxShadowTo || self.options.imageTarget)
const target = obj.styleTarget
if (target) {
target.classList.remove(self.options.hoverClass)
target.style.boxShadow = ''
if (typeof self.options.beforeLeaveCallback === 'function') self.options.beforeLeaveCallback.call(obj.styleTarget, obj, self.options, event)
target.classList.remove(self.options.activeClass)
__removeDynamicStylesFromElement(obj.styleTarget, self.options.styles.dynamic, self.options.styles.static, obj.palette)
if (typeof self.options.afterLeaveCallback === 'function') self.options.afterLeaveCallback.call(obj.styleTarget, obj, self.options, event)
}

@@ -155,22 +217,44 @@ }

/**
* Attaches Event listeners to the hoverTargets
* Attaches Event listeners to the eventTargets
* @function
* @name palettify#attachEventListeners
*/
attachEventListeners () {
const {enterEvent, leaveEvent} = self.options
self.elements.forEach(obj => {
obj.enterHandler = self.enterHandler(obj)
obj.leaveHandler = self.leaveHandler(obj)
obj.hoverTarget.addEventListener(enterEvent, obj.enterHandler, false)
obj.hoverTarget.addEventListener(leaveEvent, obj.leaveHandler, false)
let {enterEvent, leaveEvent} = self.options
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent
self.data.forEach(obj => {
obj.enterHandler = self.generateEnterHandler(obj)
obj.leaveHandler = self.generateLeaveHandler(obj)
enterEvent.forEach((event) => {
obj.eventTarget.addEventListener(event, obj.enterHandler, false)
})
leaveEvent.forEach((event) => {
obj.eventTarget.addEventListener(event, obj.leaveHandler, false)
})
})
},
/**
* Detaches event listeners from hoverTargets
* Detaches event listeners from eventTargets
* @function
* @name palettify#detachEventListeners
*/
detachEventListeners () {
const
let
{enterEvent, leaveEvent} = self.options
self.elements.forEach(obj => {
obj.hoverTarget.removeEventListener(enterEvent, obj.enterHandler, false)
obj.hoverTarget.removeEventListener(leaveEvent, obj.leaveHandler, false)
enterEvent = !Array.isArray(enterEvent) ? [enterEvent] : enterEvent
leaveEvent = !Array.isArray(leaveEvent) ? [leaveEvent] : leaveEvent
self.data.forEach(obj => {
enterEvent.forEach((event) => {
obj.eventTarget.removeEventListener(event, obj.enterHandler, false)
})
leaveEvent.forEach((event) => {
obj.eventTarget.removeEventListener(event, obj.leaveHandler, false)
})
})

@@ -181,2 +265,6 @@ },

* Gets fired when creating Palettify in the first place
* @constructs
* @param {DefaultOptions} opts - Options to pass to palettify
* @name palettify#init
* @return palettify
*/

@@ -186,7 +274,7 @@ init (opts) {

self.options = Object.assign({}, __defaults, opts)
__mergeOptions(opts)
self.collectElements()
// Attach boxShadow's to all images
self.attachBoxShadowToCollection()
// Collect colors and attach boxShadow's to all images
self.extractColorsAndAttachStyles()
// Attach event listeners to all hovered elements.

@@ -201,6 +289,10 @@ self.attachEventListeners()

* Destroys the Palettify and cleans up after it self.
* @function
* @name palettify#destroy
* @param {Boolean} [cleanUp = true]
*/
destroy () {
destroy (cleanUp = true) {
self.detachEventListeners()
self.elements = []
cleanUp && self.cleanUp()
self.data = []
isInitialized = false

@@ -211,2 +303,4 @@ },

* Destroy and then reinitialize it.
* @function
* @name palettify#reInit
*/

@@ -218,7 +312,21 @@ reInit () {

/**
* Cleans up the dom after the plugin is destroyed. Removes all styles.static
* @function
* @name palettify#cleanUp
*/
cleanUp () {
self.data.forEach(obj => {
for (let prop in self.options.styles.static) {
if (self.options.styles.static.hasOwnProperty(prop)) {
obj.styleTarget.style[prop] = ''
}
}
})
},
/**
* Set new options. Merges then with the old ones.
* Takes an options object and a reInit boolean.
* reInit defaults to True
* @param {Object} options - New options to override the old ones
* @param {Boolean} reInit - Should the plugin reInit? Defaults to true
* @param {Boolean} [reInit = true] - Should the plugin reInit? Defaults to true
* @function
* @name palettify#setOptions
*/

@@ -231,2 +339,3 @@ setOptions (options, reInit = true) {

* Is the plugin initialized
* @name palettify#isInitialized
* @return {boolean}

@@ -239,5 +348,161 @@ */

/**
* Extract the colors from image tag or Background-image inline style
* @param {HTMLElement} paletteTarget - The palette target to get the colors form
* @param {Number} colorsToExtract - Number of colors to extract
* @return {Promise} Returns promise when image is loaded with an array of RGB colors
* @private
*/
function __extractColors (paletteTarget, colorsToExtract) {
let image = __sanitizeImage(paletteTarget)
return new Promise(resolve => {
if (!isImageLoaded(image)) {
image.onload = function () {
resolve(new ColorThief().getPalette(image, colorsToExtract))
}
} else {
resolve(new ColorThief().getPalette(image, colorsToExtract))
}
})
}
/**
* Transform all colors in the palette to RGBA colors using the supplied opacities in {@see palettify#options.opacities}
* @param {Array} palette - Color palette of the current obj
* @param {Array} opacities - Array of opacities for each color
* @private
*/
function __opacifyPalette (palette, opacities) {
return palette.map((color, i) => {
let opacity = 1
if (Array.isArray(opacities) && opacities[i]) {
opacity = opacities[i]
}
return __getRgbaColor(color, opacity)
})
}
/**
* Returns the rgba color with applied opacity
* @param {Array} color
* @param {String | Number} opacity
* @return {string}
* @private
*/
function __getRgbaColor (color, opacity) {
const
r = color[0],
g = color[1],
b = color[2]
return `rgba(${r}, ${g}, ${b}, ${opacity})`
}
/**
* Adds styles to an element.
* @param target
* @param {Object} styles
* @param palette
* @param staticCallback
* @param obj
* @private
*/
function __attachStylesToElement ({target, styles, palette, staticCallback = null, obj}) {
for (const prop in styles) {
if (styles.hasOwnProperty(prop)) {
target.style[prop] = render(styles[prop], palette)
}
}
staticCallback && staticCallback.call(target, obj, self.options)
}
/**
* Removes all dynamic styles from an element.
* If the staticStyle has the same prop as dynamicStyle, we set the prop to be the static style.
* @param {HTMLElement} target - The Target to remove styles from
* @param {Object} dynamicStyles - Dynamic styles to apply
* @param {Object} staticStyles - Static styles to apply
* @param {Array} palette - Array of available palettes
* @private
*/
function __removeDynamicStylesFromElement (target, dynamicStyles, staticStyles, palette) {
for (const prop in dynamicStyles) {
if (dynamicStyles.hasOwnProperty(prop) && !staticStyles.hasOwnProperty(prop)) {
target.style[prop] = ''
} else if (staticStyles.hasOwnProperty(prop)) {
target.style[prop] = render(staticStyles[prop], palette)
}
}
}
/**
* Merges the options and throws errors where necessary
* @param {DefaultOptions} options
* @private
*/
function __mergeOptions (options) {
self.options = merge(__defaults, options, {clone: true, arrayMerge: __arrayMerge})
Object.keys(self.options).forEach(opt => {
if (self.options[opt] instanceof Error) {
throw self.options[opt]
}
})
}
function __getBrightness (rgb) {
return (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000
}
function __isDark (color) {
return __getBrightness(color) < 170 // 128 by default, but 170 gives better results
}
function __getInvertedColors (palette, colors) {
return palette.map(color => __isDark(color) ? colors.light : colors.dark)
}
function __getUrl (url) {
const el = document.createElement('a')
el.href = url
return el
}
function __isCORS (src) {
return document.location.host !== __getUrl(src).host
}
function isImageLoaded (img) {
if (!img.complete) return false
if (typeof img.naturalWidth !== 'undefined' && img.naturalWidth === 0) {
return false
}
// No other way of checking: assume it's ok.
return true
}
function __sanitizeImage (imgElement) {
let
cachedImg = imgElement,
isNotIMG = cachedImg.tagName !== 'IMG',
src = isNotIMG ? imgElement.style.backgroundImage.replace('url(', '').replace(')', '').replace(/"/gi, '') : imgElement.src,
isCors = __isCORS(src)
if (!cachedImg) throw Error('Target is not an element', cachedImg)
// Our sample is not a img tag so we try to get its background image.
if (isNotIMG || (isCors && !cachedImg.crossOrigin)) {
if (isNotIMG && !cachedImg.style.backgroundImage) throw Error('Tag provided is not an image and does not have a background-image style attached to it.')
cachedImg = new Image(imgElement.naturalWidth, imgElement.naturalHeight)
isCors && (cachedImg.crossOrigin = 'anonymous')
cachedImg.src = src
}
return cachedImg
}
function __arrayMerge (destArray, sourceArray, opts) {
return sourceArray
}
return self
}
export default palettify
export default createPalettify
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc