framework7
Version:
Full featured mobile HTML framework for building iOS & Android apps
871 lines (857 loc) • 24 kB
JavaScript
import { extend, colorRgbToHex, colorRgbToHsl, colorHslToHsb, colorHslToRgb, colorHsbToHsl, colorHexToRgb, nextTick, deleteProps } from '../../shared/utils.js';
import Framework7Class from '../../shared/class.js';
import $ from '../../shared/dom7.js';
import { getDevice } from '../../shared/get-device.js';
import moduleAlphaSlider from './modules/alpha-slider.js';
import moduleCurrentColor from './modules/current-color.js';
import moduleHex from './modules/hex.js';
import moduleHsbSliders from './modules/hsb-sliders.js';
import moduleHueSlider from './modules/hue-slider.js';
import moduleBrightnessSlider from './modules/brightness-slider.js';
import modulePalette from './modules/palette.js';
import moduleInitialCurrentColors from './modules/initial-current-colors.js';
import moduleRgbBars from './modules/rgb-bars.js';
import moduleRgbSliders from './modules/rgb-sliders.js';
import moduleSbSpectrum from './modules/sb-spectrum.js';
import moduleHsSpectrum from './modules/hs-spectrum.js';
import moduleWheel from './modules/wheel.js';
/** @jsx $jsx */
import $jsx from '../../shared/$jsx.js';
class ColorPicker extends Framework7Class {
constructor(app, params) {
if (params === void 0) {
params = {};
}
super(params, [app]);
const self = this;
self.params = extend({}, app.params.colorPicker, params);
let $containerEl;
if (self.params.containerEl) {
$containerEl = $(self.params.containerEl);
if ($containerEl.length === 0) return self;
}
let $inputEl;
if (self.params.inputEl) {
$inputEl = $(self.params.inputEl);
}
let $targetEl;
if (self.params.targetEl) {
$targetEl = $(self.params.targetEl);
}
extend(self, {
app,
$containerEl,
containerEl: $containerEl && $containerEl[0],
inline: $containerEl && $containerEl.length > 0,
$inputEl,
inputEl: $inputEl && $inputEl[0],
$targetEl,
targetEl: $targetEl && $targetEl[0],
initialized: false,
opened: false,
url: self.params.url,
modules: {
'alpha-slider': moduleAlphaSlider,
'current-color': moduleCurrentColor,
hex: moduleHex,
// eslint-disable-line
'hsb-sliders': moduleHsbSliders,
'hue-slider': moduleHueSlider,
'brightness-slider': moduleBrightnessSlider,
palette: modulePalette,
// eslint-disable-line
'initial-current-colors': moduleInitialCurrentColors,
'rgb-bars': moduleRgbBars,
'rgb-sliders': moduleRgbSliders,
'sb-spectrum': moduleSbSpectrum,
'hs-spectrum': moduleHsSpectrum,
wheel: moduleWheel // eslint-disable-line
}
});
function onInputClick() {
self.open();
}
function onInputFocus(e) {
e.preventDefault();
}
function onTargetClick() {
self.open();
}
function onHtmlClick(e) {
if (self.destroyed || !self.params) return;
if (self.params.openIn === 'page') return;
const $clickTargetEl = $(e.target);
if (!self.opened || self.closing) return;
if ($clickTargetEl.closest('[class*="backdrop"]').length) return;
if ($clickTargetEl.closest('.color-picker-popup, .color-picker-popover').length) return;
if ($inputEl && $inputEl.length > 0) {
if ($clickTargetEl[0] !== $inputEl[0] && $clickTargetEl.closest('.sheet-modal').length === 0) {
self.close();
}
} else if ($(e.target).closest('.sheet-modal').length === 0) {
self.close();
}
}
// Events
extend(self, {
attachInputEvents() {
self.$inputEl.on('click', onInputClick);
if (self.params.inputReadOnly) {
self.$inputEl.on('focus mousedown', onInputFocus);
if (self.$inputEl[0]) {
self.$inputEl[0].f7ValidateReadonly = true;
}
}
},
detachInputEvents() {
self.$inputEl.off('click', onInputClick);
if (self.params.inputReadOnly) {
self.$inputEl.off('focus mousedown', onInputFocus);
if (self.$inputEl[0]) {
delete self.$inputEl[0].f7ValidateReadonly;
}
}
},
attachTargetEvents() {
self.$targetEl.on('click', onTargetClick);
},
detachTargetEvents() {
self.$targetEl.off('click', onTargetClick);
},
attachHtmlEvents() {
app.on('click', onHtmlClick);
},
detachHtmlEvents() {
app.off('click', onHtmlClick);
}
});
self.init();
return self;
}
get view() {
const {
$inputEl,
$targetEl,
app,
params
} = this;
let view;
if (params.view) {
view = params.view;
} else {
if ($inputEl) {
view = $inputEl.parents('.view').length && $inputEl.parents('.view')[0].f7View;
}
if (!view && $targetEl) {
view = $targetEl.parents('.view').length && $targetEl.parents('.view')[0].f7View;
}
}
if (!view) view = app.views.main;
return view;
}
attachEvents() {
const self = this;
self.centerModules = self.centerModules.bind(self);
if (self.params.centerModules) {
self.app.on('resize', self.centerModules);
}
}
detachEvents() {
const self = this;
if (self.params.centerModules) {
self.app.off('resize', self.centerModules);
}
}
centerModules() {
const self = this;
if (!self.opened || !self.$el || self.inline) return;
const $pageContentEl = self.$el.find('.page-content');
if (!$pageContentEl.length) return;
const {
scrollHeight,
offsetHeight
} = $pageContentEl[0];
if (scrollHeight <= offsetHeight) {
$pageContentEl.addClass('justify-content-center');
} else {
$pageContentEl.removeClass('justify-content-center');
}
}
initInput() {
const self = this;
if (!self.$inputEl) return;
if (self.params.inputReadOnly) self.$inputEl.prop('readOnly', true);
}
getModalType() {
const self = this;
const {
app,
modal,
params
} = self;
const {
openIn,
openInPhone
} = params;
const device = getDevice();
if (modal && modal.type) return modal.type;
if (openIn !== 'auto') return openIn;
if (self.inline) return null;
if (device.ios) {
return device.ipad ? 'popover' : openInPhone;
}
if (app.width >= 768) {
return 'popover';
}
return openInPhone;
}
formatValue() {
const self = this;
const {
value
} = self;
if (self.params.formatValue) {
return self.params.formatValue.call(self, value);
}
return value.hex;
}
// eslint-disable-next-line
normalizeHsValues(arr) {
return [Math.floor(arr[0] * 10) / 10, Math.floor(arr[1] * 1000) / 1000, Math.floor(arr[2] * 1000) / 1000];
}
setValue(value, updateModules) {
if (value === void 0) {
value = {};
}
if (updateModules === void 0) {
updateModules = true;
}
const self = this;
if (typeof value === 'undefined') return;
let {
hex,
rgb,
hsl,
hsb,
alpha = 1,
hue,
rgba,
hsla
} = self.value || {};
const needChangeEvent = self.value || !self.value && !self.params.value;
let valueChanged;
Object.keys(value).forEach(k => {
if (!self.value || typeof self.value[k] === 'undefined') {
valueChanged = true;
return;
}
const v = value[k];
if (Array.isArray(v)) {
v.forEach((subV, subIndex) => {
if (subV !== self.value[k][subIndex]) {
valueChanged = true;
}
});
} else if (v !== self.value[k]) {
valueChanged = true;
}
});
if (!valueChanged) return;
if (value.rgb || value.rgba) {
const [r, g, b, a = alpha] = value.rgb || value.rgba;
rgb = [r, g, b];
hex = colorRgbToHex(...rgb);
hsl = colorRgbToHsl(...rgb);
hsb = colorHslToHsb(...hsl);
hsl = self.normalizeHsValues(hsl);
hsb = self.normalizeHsValues(hsb);
hue = hsb[0];
alpha = a;
rgba = [rgb[0], rgb[1], rgb[2], a];
hsla = [hsl[0], hsl[1], hsl[2], a];
}
if (value.hsl || value.hsla) {
const [h, s, l, a = alpha] = value.hsl || value.hsla;
hsl = [h, s, l];
rgb = colorHslToRgb(...hsl);
hex = colorRgbToHex(...rgb);
hsb = colorHslToHsb(...hsl);
hsl = self.normalizeHsValues(hsl);
hsb = self.normalizeHsValues(hsb);
hue = hsb[0];
alpha = a;
rgba = [rgb[0], rgb[1], rgb[2], a];
hsla = [hsl[0], hsl[1], hsl[2], a];
}
if (value.hsb) {
const [h, s, b, a = alpha] = value.hsb;
hsb = [h, s, b];
hsl = colorHsbToHsl(...hsb);
rgb = colorHslToRgb(...hsl);
hex = colorRgbToHex(...rgb);
hsl = self.normalizeHsValues(hsl);
hsb = self.normalizeHsValues(hsb);
hue = hsb[0];
alpha = a;
rgba = [rgb[0], rgb[1], rgb[2], a];
hsla = [hsl[0], hsl[1], hsl[2], a];
}
if (value.hex) {
rgb = colorHexToRgb(value.hex);
hex = colorRgbToHex(...rgb);
hsl = colorRgbToHsl(...rgb);
hsb = colorHslToHsb(...hsl);
hsl = self.normalizeHsValues(hsl);
hsb = self.normalizeHsValues(hsb);
hue = hsb[0];
rgba = [rgb[0], rgb[1], rgb[2], alpha];
hsla = [hsl[0], hsl[1], hsl[2], alpha];
}
if (typeof value.alpha !== 'undefined') {
alpha = value.alpha;
if (typeof rgb !== 'undefined') {
rgba = [rgb[0], rgb[1], rgb[2], alpha];
}
if (typeof hsl !== 'undefined') {
hsla = [hsl[0], hsl[1], hsl[2], alpha];
}
}
if (typeof value.hue !== 'undefined') {
const [h, s, l] = hsl; // eslint-disable-line
hsl = [value.hue, s, l];
hsb = colorHslToHsb(...hsl);
rgb = colorHslToRgb(...hsl);
hex = colorRgbToHex(...rgb);
hsl = self.normalizeHsValues(hsl);
hsb = self.normalizeHsValues(hsb);
hue = hsb[0];
rgba = [rgb[0], rgb[1], rgb[2], alpha];
hsla = [hsl[0], hsl[1], hsl[2], alpha];
}
self.value = {
hex,
alpha,
hue,
rgb,
hsl,
hsb,
rgba,
hsla
};
if (!self.initialValue) self.initialValue = extend({}, self.value);
self.updateValue(needChangeEvent);
if (self.opened && updateModules) {
self.updateModules();
}
}
getValue() {
const self = this;
return self.value;
}
updateValue(fireEvents) {
if (fireEvents === void 0) {
fireEvents = true;
}
const self = this;
const {
$inputEl,
value,
$targetEl
} = self;
if ($targetEl && self.params.targetElSetBackgroundColor) {
const {
rgba
} = value;
$targetEl.css('background-color', `rgba(${rgba.join(', ')})`);
}
if (fireEvents) {
self.emit('local::change colorPickerChange', self, value);
}
if ($inputEl && $inputEl.length) {
const inputValue = self.formatValue(value);
if ($inputEl && $inputEl.length) {
$inputEl.val(inputValue);
if (fireEvents) {
$inputEl.trigger('change');
}
}
}
}
updateModules() {
const self = this;
const {
modules
} = self;
self.params.modules.forEach(m => {
if (typeof m === 'string' && modules[m] && modules[m].update) {
modules[m].update(self);
} else if (m && m.update) {
m.update(self);
}
});
}
update() {
const self = this;
self.updateModules();
}
renderPicker() {
const self = this;
const {
params,
modules
} = self;
let html = '';
params.modules.forEach(m => {
if (typeof m === 'string' && modules[m] && modules[m].render) {
html += modules[m].render(self);
} else if (m && m.render) {
html += m.render(self);
}
});
return html;
}
renderNavbar() {
const self = this;
if (self.params.renderNavbar) {
return self.params.renderNavbar.call(self, self);
}
const {
openIn,
navbarTitleText,
navbarBackLinkText,
navbarCloseText
} = self.params;
return $jsx("div", {
class: "navbar"
}, $jsx("div", {
class: "navbar-bg"
}), $jsx("div", {
class: "navbar-inner sliding"
}, openIn === 'page' && $jsx("div", {
class: "left"
}, $jsx("a", {
class: "link back"
}, $jsx("i", {
class: "icon icon-back"
}), $jsx("span", {
class: "if-not-md"
}, navbarBackLinkText))), $jsx("div", {
class: "title"
}, navbarTitleText), openIn !== 'page' && $jsx("div", {
class: "right"
}, $jsx("a", {
class: "link popup-close",
"data-popup": ".color-picker-popup"
}, navbarCloseText))));
}
renderToolbar() {
const self = this;
if (self.params.renderToolbar) {
return self.params.renderToolbar.call(self, self);
}
return $jsx("div", {
class: "toolbar toolbar-top"
}, $jsx("div", {
class: "toolbar-inner"
}, $jsx("div", {
class: "left"
}), $jsx("div", {
class: "right"
}, $jsx("a", {
class: "link sheet-close popover-close",
"data-sheet": ".color-picker-sheet-modal",
"data-popover": ".color-picker-popover"
}, self.params.toolbarCloseText))));
}
renderInline() {
const self = this;
const {
cssClass,
groupedModules
} = self.params;
return $jsx("div", {
class: `color-picker color-picker-inline ${groupedModules ? 'color-picker-grouped-modules' : ''} ${cssClass || ''}`
}, self.renderPicker());
}
renderSheet() {
const self = this;
const {
cssClass,
toolbarSheet,
groupedModules
} = self.params;
return $jsx("div", {
class: `sheet-modal color-picker color-picker-sheet-modal ${groupedModules ? 'color-picker-grouped-modules' : ''} ${cssClass || ''}`
}, toolbarSheet && self.renderToolbar(), $jsx("div", {
class: "sheet-modal-inner"
}, $jsx("div", {
class: "page-content"
}, self.renderPicker())));
}
renderPopover() {
const self = this;
const {
cssClass,
toolbarPopover,
groupedModules
} = self.params;
return $jsx("div", {
class: `popover color-picker-popover ${cssClass || ''}`
}, $jsx("div", {
class: "popover-inner"
}, $jsx("div", {
class: `color-picker ${groupedModules ? 'color-picker-grouped-modules' : ''}`
}, toolbarPopover && self.renderToolbar(), $jsx("div", {
class: "page-content"
}, self.renderPicker()))));
}
renderPopup() {
const self = this;
const {
cssClass,
navbarPopup,
groupedModules
} = self.params;
return $jsx("div", {
class: `popup color-picker-popup ${cssClass || ''}`
}, $jsx("div", {
class: "page"
}, navbarPopup && self.renderNavbar(), $jsx("div", {
class: `color-picker ${groupedModules ? 'color-picker-grouped-modules' : ''}`
}, $jsx("div", {
class: "page-content"
}, self.renderPicker()))));
}
renderPage() {
const self = this;
const {
cssClass,
groupedModules
} = self.params;
return $jsx("div", {
class: `page color-picker-page ${cssClass || ''}`,
"data-name": "color-picker-page"
}, self.renderNavbar(), $jsx("div", {
class: `color-picker ${groupedModules ? 'color-picker-grouped-modules' : ''}`
}, $jsx("div", {
class: "page-content"
}, self.renderPicker())));
}
// eslint-disable-next-line
render() {
const self = this;
const {
params
} = self;
if (params.render) return params.render.call(self);
if (self.inline) return self.renderInline();
if (params.openIn === 'page') {
return self.renderPage();
}
const modalType = self.getModalType();
if (modalType === 'popover') return self.renderPopover();
if (modalType === 'sheet') return self.renderSheet();
if (modalType === 'popup') return self.renderPopup();
}
onOpen() {
const self = this;
const {
initialized,
$el,
app,
$inputEl,
inline,
value,
params,
modules
} = self;
self.closing = false;
self.opened = true;
self.opening = true;
// Init main events
self.attachEvents();
params.modules.forEach(m => {
if (typeof m === 'string' && modules[m] && modules[m].init) {
modules[m].init(self);
} else if (m && m.init) {
m.init(self);
}
});
const updateValue = !value && params.value;
// Set value
if (!initialized) {
if (value) self.setValue(value);else if (params.value) {
self.setValue(params.value, false);
} else if (!params.value) {
self.setValue({
hex: '#ff0000'
}, false);
}
} else if (value) {
self.initialValue = extend({}, value);
self.setValue(value, false);
}
// Update input value
if (updateValue) self.updateValue();
self.updateModules();
// Center modules
if (params.centerModules) {
self.centerModules();
}
// Extra focus
if (!inline && $inputEl && $inputEl.length && app.theme === 'md') {
$inputEl.trigger('focus');
}
self.initialized = true;
// Trigger events
if ($el) {
$el.trigger('colorpicker:open');
}
if ($inputEl) {
$inputEl.trigger('colorpicker:open');
}
self.emit('local::open colorPickerOpen', self);
}
onOpened() {
const self = this;
self.opening = false;
if (self.$el) {
self.$el.trigger('colorpicker:opened');
}
if (self.$inputEl) {
self.$inputEl.trigger('colorpicker:opened');
}
self.emit('local::opened colorPickerOpened', self);
}
onClose() {
const self = this;
const {
app,
params,
modules
} = self;
self.opening = false;
self.closing = true;
// Detach events
self.detachEvents();
if (self.$inputEl) {
if (app.theme === 'md') {
self.$inputEl.trigger('blur');
} else {
const validate = self.$inputEl.attr('validate');
const required = self.$inputEl.attr('required');
if (validate && required) {
app.input.validate(self.$inputEl);
}
}
}
params.modules.forEach(m => {
if (typeof m === 'string' && modules[m] && modules[m].destroy) {
modules[m].destroy(self);
} else if (m && m.destroy) {
m.destroy(self);
}
});
if (self.$el) {
self.$el.trigger('colorpicker:close');
}
if (self.$inputEl) {
self.$inputEl.trigger('colorpicker:close');
}
self.emit('local::close colorPickerClose', self);
}
onClosed() {
const self = this;
self.opened = false;
self.closing = false;
if (!self.inline) {
nextTick(() => {
if (self.modal && self.modal.el && self.modal.destroy) {
if (!self.params.routableModals) {
self.modal.destroy();
}
}
delete self.modal;
});
}
if (self.$el) {
self.$el.trigger('colorpicker:closed');
}
if (self.$inputEl) {
self.$inputEl.trigger('colorpicker:closed');
}
self.emit('local::closed colorPickerClosed', self);
}
open() {
const self = this;
const {
app,
opened,
inline,
$inputEl,
$targetEl,
params
} = self;
if (opened) return;
if (inline) {
self.$el = $(self.render());
self.$el[0].f7ColorPicker = self;
self.$containerEl.append(self.$el);
self.onOpen();
self.onOpened();
return;
}
const colorPickerContent = self.render();
if (params.openIn === 'page') {
self.view.router.navigate({
url: self.url,
route: {
content: colorPickerContent,
path: self.url,
on: {
pageBeforeIn(e, page) {
self.$el = page.$el.find('.color-picker');
self.$el[0].f7ColorPicker = self;
self.onOpen();
},
pageAfterIn() {
self.onOpened();
},
pageBeforeOut() {
self.onClose();
},
pageAfterOut() {
self.onClosed();
if (self.$el && self.$el[0]) {
self.$el[0].f7ColorPicker = null;
delete self.$el[0].f7ColorPicker;
}
}
}
}
});
} else {
const modalType = self.getModalType();
let backdrop = params.backdrop;
if (backdrop === null || typeof backdrop === 'undefined') {
if (modalType === 'popover' && app.params.popover.backdrop !== false) backdrop = true;
if (modalType === 'popup') backdrop = true;
}
const modalParams = {
targetEl: $targetEl || $inputEl,
scrollToEl: params.scrollToInput ? $targetEl || $inputEl : undefined,
content: colorPickerContent,
backdrop,
closeByBackdropClick: params.closeByBackdropClick,
on: {
open() {
const modal = this;
self.modal = modal;
self.$el = modalType === 'popover' || modalType === 'popup' ? modal.$el.find('.color-picker') : modal.$el;
self.$el[0].f7ColorPicker = self;
self.onOpen();
},
opened() {
self.onOpened();
},
close() {
self.onClose();
},
closed() {
self.onClosed();
if (self.$el && self.$el[0]) {
self.$el[0].f7ColorPicker = null;
delete self.$el[0].f7ColorPicker;
}
}
}
};
if (modalType === 'popup') {
modalParams.push = params.popupPush;
modalParams.swipeToClose = params.popupSwipeToClose;
}
if (modalType === 'sheet') {
modalParams.push = params.sheetPush;
modalParams.swipeToClose = params.sheetSwipeToClose;
}
if (params.routableModals && self.view) {
self.view.router.navigate({
url: self.url,
route: {
path: self.url,
[modalType]: modalParams
}
});
} else {
self.modal = app[modalType].create(modalParams);
self.modal.open();
}
}
}
close() {
const self = this;
const {
opened,
inline
} = self;
if (!opened) return;
if (inline) {
self.onClose();
self.onClosed();
return;
}
if (self.params.routableModals && self.view || self.params.openIn === 'page') {
self.view.router.back();
} else {
self.modal.close();
}
}
init() {
const self = this;
self.initInput();
if (self.inline) {
self.open();
self.emit('local::init colorPickerInit', self);
return;
}
if (!self.initialized && self.params.value) {
self.setValue(self.params.value);
}
// Attach input Events
if (self.$inputEl) {
self.attachInputEvents();
}
if (self.$targetEl) {
self.attachTargetEvents();
}
if (self.params.closeByOutsideClick) {
self.attachHtmlEvents();
}
self.emit('local::init colorPickerInit', self);
}
destroy() {
const self = this;
if (self.destroyed) return;
const {
$el
} = self;
self.emit('local::beforeDestroy colorPickerBeforeDestroy', self);
if ($el) $el.trigger('colorpicker:beforedestroy');
self.close();
// Detach Events
self.detachEvents();
if (self.$inputEl) {
self.detachInputEvents();
}
if (self.$targetEl) {
self.detachTargetEvents();
}
if (self.params.closeByOutsideClick) {
self.detachHtmlEvents();
}
if ($el && $el.length) delete self.$el[0].f7ColorPicker;
deleteProps(self);
self.destroyed = true;
}
}
export default ColorPicker;