<commit_before>/**
 * Picker class.
 * A controller for draggable.
 * Because it has some intermediate API:
 * - update
 * - value
 *
 * Note that it’s not an extension of draggable due to method names conflict, like update.
 */

var Draggable = require('draggy');
var defineState = require('define-state');
var emit = require('emmy/emit');
var on = require('emmy/on');
var css = require('mucss/css');
var Emitter = require('events');
var isFn = require('is-function');
var isNumber = require('is-number');
var round = require('mumath/round');
var between = require('mumath/between');
var loop = require('mumath/loop');
var getUid = require('get-uid');
var isArray = require('is-array');


module.exports = Picker;


var doc = document, root = document.documentElement;


/** The most precise step available. */
var MIN_STEP = 0.00001;


/** Default pageup/pagedown size, in steps */
var PAGE = 5;


/**
 * Picker instance
 *
 * @constructor
 */
function Picker (el, options) {
	if (!(this instanceof Picker)) return new Picker(el, options);

	var self = this;

	//ensure element
	if (!el) {
		el = doc.createElement('div');
	}
	el.classList.add('slidy-picker');
	self.element = el;

	if (options.pickerClass) el.classList.add(options.pickerClass);

	//generate self id
	self.id = getUid();
	self._ns = 'slidy-picker-' + self.id;
	if (!self.element.id) self.element.id = self._ns;

	//adopt options
	if (options.max !== undefined) self.max = options.max;
	if (options.min !== undefined) self.min = options.min;
	if (options.repeat !== undefined) self.repeat = options.repeat;
	if (options.release !== undefined) self.release = options.release;
	if (options.keyboard !== undefined) self.keyboard = options.keyboard;
	if (options.aria !== undefined) self.aria = options.aria;
	if (options.scroll !== undefined) self.scroll = options.scroll;
	if (options.align !== undefined) self.align = options.align;
	if (options.point !== undefined) self.point = options.point;

	//init draggable
	self.draggable = new Draggable(el, {
		threshold: 0,
		within: options.within,
		sniperSlowdown: 0.85,
		axis: 'x',
		repeat: self.repeat,
		releaseDuration: 80
	});

	if (options.step !== undefined) self.step = options.step;
	//detect step automatically based on min/max range (1/100 by default)
	//native behaviour is always 1, so ignore it
	else {
		var range = Math.abs(self.max - self.min);
		self.step = range < 100 ? 0.01 : self.step;
	}

	if (options.snap !== undefined) self.snap = options.snap;

	//define type of picker
	defineState(self, 'type', self.type);
	self.type = options.type === undefined ? 'horizontal': options.type;

	//go enabled
	self.enable();

	//set value, if passed
	//in single pickers value is inited afterwards
	if (options.value !== undefined) self.value = options.value;
}


var proto = Picker.prototype = Object.create(Emitter.prototype);


/** Enabled/Disabled state */
proto.enable = function () {
	var self = this;

	if (self.isEnabled) return self;
	self.isEnabled = true;

	if (self.aria) {
		//ARIAs
		self.element.removeAttribute('aria-disabled');
	}

	self.element.removeAttribute('disabled');

	//events
	on(self.draggable, 'dragstart.' + self._ns, function () {
		css(root, 'cursor', 'none');
		css(this.element, 'cursor', 'none');
	});
	on(self.draggable, 'drag', function () {
		//ignore animated state to avoid collisions of value
		if (self.release && self.draggable.isAnimated) return;

		var value = self.calcValue.apply(self, self.draggable.getCoords());

		self.value = value;

		//display snapping
		if (self.snap) {
			self.renderValue(self.value);
		}

	});
	on(self.draggable, 'dragend.' + self._ns, function () {
		if (self.release) {
			self.draggable.isAnimated = true;
		}

		self.renderValue(self.value);
		css(root, 'cursor', null);
		css(this.element, 'cursor', null);
	});

	if (self.keyboard) {
		//make focusable
		self.element.setAttribute('tabindex', 0);

		//kbd events
		//borrowed from native input range mixed with multithumb range
		//@ref http://access.aol.com/dhtml-style-guide-working-group/#slidertwothumb
		self._pressedKeys = [];
		on(self.element, 'keydown.' + self._ns, function (e) {
			//track pressed keys, to do diagonal movements
			self._pressedKeys[e.which] = true;

			if (e.which >= 33 && e.which <= 40) {
				e.preventDefault();

				self.value = self.handleKeys(self._pressedKeys, self.value, self.step, self.min, self.max);

				if (self.release) self.draggable.isAnimated = true;

				self.renderValue(self.value);
			}
		});
		on(self.element, 'keyup.' + self._ns, function (e) {
			self._pressedKeys[e.which] = false;
		});
	}

	return self;
};
proto.disable = function () {
	var self = this;

	self.isEnabled = false;

	if (self.aria) {
		//ARIAs
		self.element.setAttribute('aria-disabled', true);
	}

	self.element.setAttribute('disabled', true);

	//unbind events
	off(self.element,'dragstart.' + self._ns);
	off(self.element,'drag.' + self._ns);
	off(self.element,'dragend.' + self._ns);

	if (self.keyboard) {
		//make unfocusable
		self.element.setAttribute('tabindex', -1);
		off(self.element,'keydown.' + self._ns);
		off(self.element,'keyup.' + self._ns);
	}

	return self;
};


/** Default min/max values */
proto.min = 0;
proto.max = 100;


/** Default step to bind value. It is automatically detected, if isn’t passed. */
proto.step = 1;


/** Loose snapping while drag */
proto.snap = false;


/** Animate release movement */
proto.release = false;


/** Point picker isn’t constrained by it’s shape */
proto.point = false;


/** Picker alignment relative to the mouse. Redefined by slidy, but to prevent empty value it is set to number. */
proto.align = 0.5;


/** Current picker value wrapper */
Object.defineProperties(proto, {
	value: {
		set: function (value) {
			if (value === undefined) throw Error('Picker value cannot be undefined.');

			//apply repeat
			if (this.repeat) {
				if (isArray(value)) {
					if (this.repeat === 'x') value[0] = loop(value[0], this.min[0], this.max[0]);
					else if (this.repeat === 'y') value[1] = loop(value[1], this.min[1], this.max[1]);
					else value = loop(value, this.min, this.max);
				}
				else {
					value = loop(value, this.min, this.max);
				}
			}

			//apply limiting
			value = between(value, this.min, this.max);

			//round value
			if (this.step) {
				if (isNumber(this.step)) value = round(value, this.step);
				if (isFn(this.step)) value = round(value, this.step(value));
			}

			this._value = value;

			//trigger bubbling event, like all inputs do
			this.emit('change', value);
			emit(this.element, 'change', value, true);
		},
		get: function () {
			//catch undefined value to lazy-init it with 50
			if (this._value === undefined) this.value = (this.min + this.max) * 0.5;

			return this._value;
		}
	}
});


/**
 * Move picker visually to the value passed.
 * Supposed to be redefined by type
 *
 * @param {number} value Value to render
 *
 * @return {Picker} Self instance
 */
proto.renderValue = function (value) {};


/**
 * Calc value from the picker position
 * Supposed to be redefined by type
 *
 * @return {number} Value, min..max
 */
proto.calcValue = function (x, y) {};


/**
 * Update value based on keypress. Supposed to be redefined in type of picker.
 */
proto.handleKeys = function (key, value, step) {};


/** Update self size, pin & position, according to the value */
proto.update = function () {
	//update pin - may depend on element’s size
	if (this.point) {
		this.draggable.pin = [
			this.draggable.offsets.width * this.align,
			this.draggable.offsets.height * this.align
		];
	}

	//update draggable limits
	this.draggable.update();

	//update position according to the value
	this.renderValue(this.value);

	return this;
};


/** Move picker to the x, y relative coordinates */
proto.move = function (x, y) {
	var self = this;
	//correct point placement
	if (self.point) {
		var cx = this.draggable.pin.width * this.align;
		var cy = this.draggable.pin.height * this.align;
		x = x - this.draggable.pin[0] - cx;
		y = y - this.draggable.pin[1] - cy;
	}

	this.draggable.move(x, y);

	//set value
	this.value = this.calcValue(x, y);

	return this;
};


/**
 * Move picker to the point of click with the centered drag point
 */
proto.startDrag = function (e) {
	var self = this;

	//update drag limits based off event passed
	self.draggable.setTouch(e).update(e);

	//start drag
	//ignore if already drags
	if (self.draggable.state !== 'drag') {
		self.draggable.state = 'drag';
	}

	//centrize picker
	self.draggable.innerOffsetX = self.draggable.pin[0] + self.draggable.pin.width * 0.5;
	self.draggable.innerOffsetY = self.draggable.pin[1] + self.draggable.pin.height * 0.5;

	//emulate move
	self.draggable.drag(e);

	return this;
};


/** Make it active. */
proto.focus = function () {
	var self = this;
	self.element.focus();
};
proto.blur = function () {
	var self = this;
	self.element.blur();
};


/**
 * Placing type
 * @enum {string}
 * @default 'horizontal'
 */
proto.type = {
	horizontal: function () {
		var self = this;

		self.draggable.axis = 'x';

		//place pickers according to the value
		self.renderValue = function (value) {
			var	lims = self.draggable.limits,
				scope = lims.right - lims.left,
				range = self.max - self.min,
				ratio = (value - self.min) / range,
				x = ratio * scope;

			self.move(x);

			return self;
		};

		//round value on each drag
		self.calcValue = function (x) {
			var lims = self.draggable.limits,
				scope = lims.right - lims.left,
				normalValue = (x - lims.left) / scope;

			return normalValue * (self.max - self.min) + self.min;
		};

		self.handleKeys = handle1dkeys;
	},
	vertical: function () {
		var self = this;
		self.draggable.axis = 'y';

		//place pickers according to the value
		self.renderValue = function (value) {
			var	lims = self.draggable.limits,
				scope = lims.bottom - lims.top,
				range = self.max - self.min,
				ratio = (-value + self.max) / range,
				y = ratio * scope;
			self.move(null, y);

			return self;
		};
<commit_msg>Predefine value on init<commit_after>/**
 * Picker class.
 * A controller for draggable.
 * Because it has some intermediate API:
 * - update
 * - value
 *
 * Note that it’s not an extension of draggable due to method names conflict, like update.
 */

var Draggable = require('draggy');
var defineState = require('define-state');
var emit = require('emmy/emit');
var on = require('emmy/on');
var css = require('mucss/css');
var Emitter = require('events');
var isFn = require('is-function');
var isNumber = require('is-number');
var round = require('mumath/round');
var between = require('mumath/between');
var loop = require('mumath/loop');
var getUid = require('get-uid');
var isArray = require('is-array');


module.exports = Picker;


var doc = document, root = document.documentElement;


/** The most precise step available. */
var MIN_STEP = 0.00001;


/** Default pageup/pagedown size, in steps */
var PAGE = 5;


/**
 * Picker instance
 *
 * @constructor
 */
function Picker (el, options) {
	if (!(this instanceof Picker)) return new Picker(el, options);

	var self = this;

	//ensure element
	if (!el) {
		el = doc.createElement('div');
	}
	el.classList.add('slidy-picker');
	self.element = el;

	if (options.pickerClass) el.classList.add(options.pickerClass);

	//generate self id
	self.id = getUid();
	self._ns = 'slidy-picker-' + self.id;
	if (!self.element.id) self.element.id = self._ns;

	//adopt options
	if (options.max !== undefined) self.max = options.max;
	if (options.min !== undefined) self.min = options.min;
	if (options.repeat !== undefined) self.repeat = options.repeat;
	if (options.release !== undefined) self.release = options.release;
	if (options.keyboard !== undefined) self.keyboard = options.keyboard;
	if (options.aria !== undefined) self.aria = options.aria;
	if (options.scroll !== undefined) self.scroll = options.scroll;
	if (options.align !== undefined) self.align = options.align;
	if (options.point !== undefined) self.point = options.point;

	//init draggable
	self.draggable = new Draggable(el, {
		threshold: 0,
		within: options.within,
		sniperSlowdown: 0.85,
		axis: 'x',
		repeat: self.repeat,
		releaseDuration: 80
	});

	if (options.step !== undefined) self.step = options.step;
	//detect step automatically based on min/max range (1/100 by default)
	//native behaviour is always 1, so ignore it
	else {
		var range = Math.abs(self.max - self.min);
		self.step = range < 100 ? 0.01 : self.step;
	}

	if (options.snap !== undefined) self.snap = options.snap;

	//define type of picker
	defineState(self, 'type', self.type);
	self.type = options.type === undefined ? 'horizontal': options.type;

	//go enabled
	self.enable();

	//set value, if passed
	if (options.value !== undefined) self.value = options.value;
	//else calc as a middle of range
	else self.value = (self.min + self.max) * 0.5;
}


var proto = Picker.prototype = Object.create(Emitter.prototype);


/** Enabled/Disabled state */
proto.enable = function () {
	var self = this;

	if (self.isEnabled) return self;
	self.isEnabled = true;

	if (self.aria) {
		//ARIAs
		self.element.removeAttribute('aria-disabled');
	}

	self.element.removeAttribute('disabled');

	//events
	on(self.draggable, 'dragstart.' + self._ns, function () {
		css(root, 'cursor', 'none');
		css(this.element, 'cursor', 'none');
	});
	on(self.draggable, 'drag', function () {
		//ignore animated state to avoid collisions of value
		if (self.release && self.draggable.isAnimated) return;

		var value = self.calcValue.apply(self, self.draggable.getCoords());

		self.value = value;

		//display snapping
		if (self.snap) {
			self.renderValue(self.value);
		}

	});
	on(self.draggable, 'dragend.' + self._ns, function () {
		if (self.release) {
			self.draggable.isAnimated = true;
		}

		self.renderValue(self.value);
		css(root, 'cursor', null);
		css(this.element, 'cursor', null);
	});

	if (self.keyboard) {
		//make focusable
		self.element.setAttribute('tabindex', 0);

		//kbd events
		//borrowed from native input range mixed with multithumb range
		//@ref http://access.aol.com/dhtml-style-guide-working-group/#slidertwothumb
		self._pressedKeys = [];
		on(self.element, 'keydown.' + self._ns, function (e) {
			//track pressed keys, to do diagonal movements
			self._pressedKeys[e.which] = true;

			if (e.which >= 33 && e.which <= 40) {
				e.preventDefault();

				self.value = self.handleKeys(self._pressedKeys, self.value, self.step, self.min, self.max);

				if (self.release) self.draggable.isAnimated = true;

				self.renderValue(self.value);
			}
		});
		on(self.element, 'keyup.' + self._ns, function (e) {
			self._pressedKeys[e.which] = false;
		});
	}

	return self;
};
proto.disable = function () {
	var self = this;

	self.isEnabled = false;

	if (self.aria) {
		//ARIAs
		self.element.setAttribute('aria-disabled', true);
	}

	self.element.setAttribute('disabled', true);

	//unbind events
	off(self.element,'dragstart.' + self._ns);
	off(self.element,'drag.' + self._ns);
	off(self.element,'dragend.' + self._ns);

	if (self.keyboard) {
		//make unfocusable
		self.element.setAttribute('tabindex', -1);
		off(self.element,'keydown.' + self._ns);
		off(self.element,'keyup.' + self._ns);
	}

	return self;
};


/** Default min/max values */
proto.min = 0;
proto.max = 100;


/** Default step to bind value. It is automatically detected, if isn’t passed. */
proto.step = 1;


/** Loose snapping while drag */
proto.snap = false;


/** Animate release movement */
proto.release = false;


/** Point picker isn’t constrained by it’s shape */
proto.point = false;


/** Picker alignment relative to the mouse. Redefined by slidy, but to prevent empty value it is set to number. */
proto.align = 0.5;


/** Current picker value wrapper */
Object.defineProperties(proto, {
	value: {
		set: function (value) {
			if (value === undefined) throw Error('Picker value cannot be undefined.');

			//apply repeat
			if (this.repeat) {
				if (isArray(value)) {
					if (this.repeat === 'x') value[0] = loop(value[0], this.min[0], this.max[0]);
					else if (this.repeat === 'y') value[1] = loop(value[1], this.min[1], this.max[1]);
					else value = loop(value, this.min, this.max);
				}
				else {
					value = loop(value, this.min, this.max);
				}
			}

			//apply limiting
			value = between(value, this.min, this.max);

			//round value
			if (this.step) {
				if (isNumber(this.step)) value = round(value, this.step);
				if (isFn(this.step)) value = round(value, this.step(value));
			}

			this._value = value;
			//trigger bubbling event, like all inputs do
			this.emit('change', value);
			emit(this.element, 'change', value, true);
		},
		get: function () {
			return this._value;
		}
	}
});


/**
 * Move picker visually to the value passed.
 * Supposed to be redefined by type
 *
 * @param {number} value Value to render
 *
 * @return {Picker} Self instance
 */
proto.renderValue = function (value) {};


/**
 * Calc value from the picker position
 * Supposed to be redefined by type
 *
 * @return {number} Value, min..max
 */
proto.calcValue = function (x, y) {};


/**
 * Update value based on keypress. Supposed to be redefined in type of picker.
 */
proto.handleKeys = function (key, value, step) {};


/** Update self size, pin & position, according to the value */
proto.update = function () {
	//update pin - may depend on element’s size
	if (this.point) {
		this.draggable.pin = [
			this.draggable.offsets.width * this.align,
			this.draggable.offsets.height * this.align
		];
	}

	//update draggable limits
	this.draggable.update();

	//update position according to the value
	this.renderValue(this.value);

	return this;
};


/** Move picker to the x, y relative coordinates */
proto.move = function (x, y) {
	var self = this;
	//correct point placement
	if (self.point) {
		var cx = this.draggable.pin.width * this.align;
		var cy = this.draggable.pin.height * this.align;
		x = x - this.draggable.pin[0] - cx;
		y = y - this.draggable.pin[1] - cy;
	}

	this.draggable.move(x, y);

	//set value
	this.value = this.calcValue(x, y);

	return this;
};


/**
 * Move picker to the point of click with the centered drag point
 */
proto.startDrag = function (e) {
	var self = this;

	//update drag limits based off event passed
	self.draggable.setTouch(e).update(e);

	//start drag
	//ignore if already drags
	if (self.draggable.state !== 'drag') {
		self.draggable.state = 'drag';
	}

	//centrize picker
	self.draggable.innerOffsetX = self.draggable.pin[0] + self.draggable.pin.width * 0.5;
	self.draggable.innerOffsetY = self.draggable.pin[1] + self.draggable.pin.height * 0.5;

	//emulate move
	self.draggable.drag(e);

	return this;
};


/** Make it active. */
proto.focus = function () {
	var self = this;
	self.element.focus();
};
proto.blur = function () {
	var self = this;
	self.element.blur();
};


/**
 * Placing type
 * @enum {string}
 * @default 'horizontal'
 */
proto.type = {
	horizontal: function () {
		var self = this;

		self.draggable.axis = 'x';

		//place pickers according to the value
		self.renderValue = function (value) {
			var	lims = self.draggable.limits,
				scope = lims.right - lims.left,
				range = self.max - self.min,
				ratio = (value - self.min) / range,
				x = ratio * scope;

			// console.log('render', value, ' : ', x)

			self.move(x);

			return self;
		};

		//round value on each drag
		self.calcValue = function (x) {
			var lims = self.draggable.limits,
				scope = lims.right - lims.left,
				normalValue = (x - lims.left) / scope;

			var value = normalValue * (self.max - self.min) + self.min;
			// console.log('calc', x, ' : ', value);

			return value;
		};

		self.handleKeys = handle1dkeys;
	},
	vertical: function () {
		var self = this;
		self.draggable.axis = 'y';

		//place pickers according to the value
		self.renderValue = function (value) {
			var	lims = self.draggable.limits,
				scope = lims.bottom - lims.top,
				range = self.max - self.min,
				ratio = (-value + self.max) / range,
				y = ratio * scope;
			self.move(null, y);

			return self;
		};

{'fraction_non_alphanumeric': 0.15767965713146617, 'fraction_numerical': 0.005681251868832851, 'mean_word_length': 3.308997208503328, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5746855', 'n_tokens_mistral': 7446, 'n_tokens_neox': 6794, 'n_words': 1838}