vercengen/components/ComponentColour.js

/**
 * Refer to <span color = "yellow">{@link ve.Component}</span> for methods or fields inherited from this Component's parent such as `.options.attributes` or `.element`.
 *
 * Basic colour input used to fetch [R, G, B] parameters. May also fetch hex value via <span color = 00ffff>getHex</span>().
 * - Functional binding: <span color=00ffff>veColour</span>().
 *
 * ##### Constructor:
 * - `arg0_value`: {@link Array}<{@link number}, {@link number}, {@link number}> | {@link string}
 * - `arg1_options`: {@link Object}
 *   - `.is_rgba=false`: {@link boolean} - If true, adds an opacity slider and handles [R, G, B, A] values.
 *
 * ##### Instance:
 * - `.v`: {@link Array}<{@link number}, {@link number}, {@link number}> - The R, G, B (and optional A) value of the present Component.
 *
 * ##### Methods:
 * - <span color=00ffff>{@link ve.Colour.getHex|getHex}</span>() | {@link string} - Returns the hex value of the present R, G, B value.
 * - <span color=00ffff>{@link ve.Colour.toString|toString}</span>() | {@link string} - Returns the R,G,B value as a string.
 *
 * @augments ve.Component
 * @memberof ve.Component
 * @type {ve.Colour}
 */
ve.Colour = class extends ve.Component { //[WIP] - Refactor at a later date
	static demo_value = [220, 160, 60];
	
	constructor (arg0_value, arg1_options) {
		//Convert from parameters
		let options = (arg1_options) ? arg1_options : {};
		let initial_value = (arg0_value) ? arg0_value : [255, 255, 255];
		super(options);
		
		//Initialise options
		options.attributes = options.attributes ? options.attributes : {};
		if (options.name === undefined) options.name = loc("ve.registry.localisation.Colour_name_default");
		
		//Logic update for RGB/alpha parsing
		let alpha_value = 1;
		let rgb_value = [255, 255, 255];
		
		if (Array.isArray(initial_value)) {
			// Handle Array Input
			if (initial_value.length === 4) {
				//[R, G, B, A]
				rgb_value = [initial_value[0], initial_value[1], initial_value[2]];
				alpha_value = initial_value[3];
				// Auto-enable RGBA mode if 4 values are passed, unless explicitly disabled
				if (options.is_rgba === undefined) options.is_rgba = true;
			} else {
				//[R, G, B]
				rgb_value = initial_value;
			}
		} else if (typeof initial_value === "string") {
			//Handle String Input
			if (initial_value.startsWith("#")) {
				if (initial_value.length > 7) {
					//#RRGGBBAA (8-digit)
					let rgba = Colour.convertHexToRGBA(initial_value);
					rgb_value = [rgba[0], rgba[1], rgba[2]];
					alpha_value = rgba[3];
					if (options.is_rgba === undefined) options.is_rgba = true;
				} else {
					//#RRGGBB (6-digit)
					rgb_value = Colour.convertHexToRGB(initial_value);
				}
			}
		}
		
		//Convert RGB array to #RRGGBB string for the HTML input
		let value = Colour.convertRGBToHex(rgb_value);
		
		//Declare local instance variables
		this.element = document.createElement("div");
		this.element.setAttribute("component", "ve-colour");
		this.element.instance = this;
		
		this.alpha = alpha_value;
		this.options = options;
		this.value = value;
		
		//Format HTML string
		let html_string = [];
		html_string.push(`<span id = "name"></span> `);
		html_string.push(`<input type = "color"${HTML.objectToAttributes(options.attributes)}>`);
		
		//Populate element and initialise handlers
		this.element.innerHTML = html_string.join("");
		
		let input_el = this.element.querySelector("input");
		input_el.value = this.value;
		if (this.options.is_rgba) input_el.style.opacity = this.alpha;
		
		input_el.addEventListener("input", (e) => {
			this.value = e.target.value; // Updates internal hex
			this.fireToBinding();
		});
		
		//RGBA Opacity Slider Logic
		if (this.options.is_rgba) {
			this.opacity_slider = new ve.Range(this.alpha * 100, {
				name: loc("ve.registry.localisation.Colour_opacity_label"),
				min: 0,
				max: 100,
				step: 1,
				style: {
					display: "inline",
					marginLeft: "0.5rem"
				},
				onuserchange: (v) => {
					this.alpha = v / 100;
					this.element.querySelector(`input[type="color"]`).style.opacity = this.alpha;
					this.fireToBinding();
				},
			});
			this.element.appendChild(this.opacity_slider.element);
		}
		
		this.name = options.name;
	}
	
	/**
	 * Returns the current [R, G, B] (or [R, G, B, A]) value.
	 * - Accessor of: {@link ve.Colour}
	 *
	 * @alias v
	 * @memberof ve.Component.ve.Colour
	 * @type {number[]}
	 */
	get v () {
		//Get RGB from the internal hex string stored in this.value
		let rgb = Colour.convertHexToRGB(this.value);
		
		if (this.options.is_rgba) {
			return [rgb[0], rgb[1], rgb[2], this.alpha];
		}
		return rgb;
	}
	
	/**
	 * Sets the current value. Accepts [R, G, B], [R, G, B, A] arrays, or Hex strings.
	 * - Accessor of: {@link ve.Colour}
	 *
	 * @alias v
	 * @memberof ve.Component.ve.Colour
	 *
	 * @param {number[]|string} arg0_value
	 */
	set v (arg0_value) {
		let input_rgb = [0, 0, 0];
		let input_alpha = 1;
		
		//1. Parse Input
		if (typeof arg0_value === "string") {
			//Hex String Handling
			if (arg0_value.startsWith("#")) {
				if (arg0_value.length > 7) {
					// #RRGGBBAA
					let rgba = Colour.convertHexToRGBA(arg0_value);
					input_rgb = [rgba[0], rgba[1], rgba[2]];
					input_alpha = rgba[3];
				} else {
					//#RRGGBB
					input_rgb = Colour.convertHexToRGB(arg0_value);
				}
			}
		} else if (Array.isArray(arg0_value)) {
			// Array Handling
			if (arg0_value.length === 4) {
				input_rgb = [arg0_value[0], arg0_value[1], arg0_value[2]];
				input_alpha = arg0_value[3];
			} else {
				input_rgb = arg0_value;
			}
		}
		
		//2. Convert RGB part to Standard Hex for HTML Input
		let hex_value = Colour.convertRGBToHex(input_rgb);
		
		//3. Update State
		this.value = hex_value;
		this.element.querySelector("input").value = this.value;
		
		//4. Update Alpha if applicable
		if (this.options.is_rgba) {
			this.alpha = input_alpha;
			this.element.querySelector("input").style.opacity = this.alpha;
			if (this.opacity_slider) {
				this.opacity_slider.v = this.alpha * 100;
			}
		}
		
		this.fireFromBinding();
	}
	
	/**
	 * Returns the hexadecimal value of {@link ve.Colour} as a string.
	 * Returns 8-digit hex if is_rgba is true.
	 * - Method of: {@link ve.Colour}
	 *
	 * @alias getHex
	 * @memberof ve.Component.ve.Colour
	 *
	 * @returns {string}
	 */
	getHex () {
		if (this.options.is_rgba) {
			// Combine current RGB with current Alpha
			return Colour.convertRGBAToHex(this.v);
		}
		return this.value; // this.value is already #RRGGBB
	}
	
	/**
	 * Returns the 'R,G,B' (or 'R,G,B,A') value of {@link ve.Colour} as a string.
	 * - Method of: {@link ve.Colour}
	 *
	 * @alias toString
	 * @memberof ve.Component.ve.Colour
	 *
	 * @returns {string}
	 */
	toString () {
		return this.v.join(",");
	}
};

//Functional binding

/**
 * @returns {ve.Colour}
 */
veColour  = function () {
	//Return statement
	return new ve.Colour(...arguments);
};