text: /*
   * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
   * http://paperjs.org/
   *
   * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
   * http://scratchdisk.com/ & http://jonathanpuckey.com/
   *
   * Distributed under the MIT license. See LICENSE file for details.
   *
   * All rights reserved.
   */
  
   /**
    * @name Component
    * @class
    */
  var Component = Base.extend(Callback, /** @lends Component# */{
  	_class: 'Component',
  	_events: [ 'onChange', 'onClick' ],
  
  	// Meta-information, by type. This is stored in _meta on the components.
  	_types: {
  		'boolean': {
  			type: 'checkbox',
  			value: 'checked'
  		},
  
  		string: {
  			type: 'text'
  		},
  
  		number: {
  			type: 'number',
  			number: true
  		},
  
  		button: {
  			type: 'button'
  		},
  
  		text: {
  			tag: 'div',
  			// This will return the native textContent through DomElement.get():
  			value: 'text'
  		},
  
  		slider: {
  			type: 'range',
  			number: true
  		},
  
  		list: {
  			tag: 'select',
  
  			setOptions: function() {
  				DomElement.removeChildren(this._input);
  				DomElement.create(Base.each(this._options, function(option) {
  					this.push('option', { value: option, text: option });
  				}, []), this._input);
  			}
  		},
  
  		color: {
  			type: 'color',
  
  			getValue: function(value) {
  				// Always convert internal string representation back to a
  				// paper.js color object.
  				return new Color(value);
  			},
  
  			setValue: function(value) {
  				// Only enfore hex values if the input field is indeed of
  				// color type. This allows sketch.paperjs.org to plug in
  				// the Spectrum.js library with alpha support.
  				return new Color(value).toCSS(
  						DomElement.get(this._input, 'type') === 'color');
  			}
  		}
  	},
  
  	initialize: function Component(obj) {
  		this._id = Component._id = (Component._id || 0) + 1;
  		this._type = obj.type in this._types
  			? obj.type
  			: 'options' in obj
  				? 'list'
  				: 'onClick' in obj
  					? 'button'
  					: typeof obj.value;
  		this._meta = this._types[this._type] || { type: this._type };
  		var that = this,
  			id = 'component-' + this._id;
  		this._dontFire = true;
  		this._input = DomElement.create(this._meta.tag || 'input', {
  			id: id,
  			type: this._meta.type,
  			events: {
  				change: function() {
  					that.setValue(
  						DomElement.get(this, that._meta.value || 'value'));
  				},
  				click: function() {
  					that.fire('click');
  				}
  			}
  		});
  		// Attach default 'change' even that delegates to palette
  		this.attach('change', function(value) {
  			if (!this._dontFire)
  				this._palette.fire('change', this, this.name, value);
  		});
  		this._element = DomElement.create('tr', [
  			'td', [this._label = DomElement.create('label', { 'for': id })],
  			'td', [this._input]
  		]);
  		Base.each(obj, function(value, key) {
  			this[key] = value;
  		}, this);
  		this._defaultValue = this._value;
  		// Start firing change events after we have initalized.
  		this._dontFire = false;
  	},
  
  	getType: function() {
  		return this._type;
  	},
  
  	getLabel: function() {
  		// Prefix wit '__' since _label is already the element.
  		return this.__label;
  	},
  
  	setLabel: function(label) {
  		this.__label = label;
  		DomElement.set(this._label, 'text', label + ':');
  	},
  
  	getOptions: function() {
  		return this._options;
  	},
  
  	setOptions: function(options) {
  		this._options = options;
  		var setOptions = this._meta.setOptions;
  		if (setOptions)
  			setOptions.call(this);
  	},
  
  	getValue: function() {
  		var value = this._value,
  			getValue = this._meta.getValue;
  		return getValue ? getValue.call(this, value) : value;
  	},
  
  	setValue: function(value) {
  		var key = this._meta.value || 'value',
  			setValue = this._meta.setValue;
  		if (setValue)
  			value = setValue.call(this, value);
  		DomElement.set(this._input, key, value);
  		// Read back and convert from input again, to make sure we're in sync
  		value = DomElement.get(this._input, key);
  		if (this._meta.number)
  			value = parseFloat(value, 10);
  		if (this._value !== value) {
  			this._value = value;
  			if (!this._dontFire)
  				this.fire('change', this.getValue());
  		}
  	},
  
  	getRange: function() {
  		return [parseFloat(DomElement.get(this._input, 'min')),
  				parseFloat(DomElement.get(this._input, 'max'))];
  	},
  
  	setRange: function(min, max) {
  		var range = Array.isArray(min) ? min : [min, max];
  		DomElement.set(this._input, { min: range[0], max: range[1] });
  	},
  
  	getMin: function() {
  		return this.getRange()[0];
  	},
  
  	setMin: function(min) {
  		this.setRange(min, this.getMax());
  	},
  
  	getMax: function() {
  		return this.getRange()[1];
  	},
  
  	setMax: function(max) {
  		this.setRange(this.getMin(), max);
  	},
  
  	getStep: function() {
  		return parseFloat(DomElement.get(this._input, 'step'));
  	},
  
  	setStep: function(step) {
  		DomElement.set(this._input, 'step', step);
  	},
  
  	reset: function() {
  		this.setValue(this._defaultValue);
  	}
  });
  