UNPKG

pencil.js

Version:

Nice modular interactive 2D drawing library.

203 lines (182 loc) 6.27 kB
import Component from "@pencil.js/component"; import Input from "@pencil.js/input"; import MouseEvent from "@pencil.js/mouse-event"; import BaseEvent from "@pencil.js/base-event"; import Rectangle from "@pencil.js/rectangle"; import Text from "@pencil.js/text"; import { constrain } from "@pencil.js/math"; import Triangle from "@pencil.js/triangle"; /** * @module Select */ const selectedKey = Symbol("_selected"); /** * Select class * <br><img src="./media/examples/select.png" alt="select demo"/> * @class * @extends {module:Input} */ export default class Select extends Input { /** * Select constructor * @param {PositionDefinition} positionDefinition - Any position * @param {Array<String>} optionsList - List of options to display * @param {InputOptions} [options] - Drawing options */ constructor (positionDefinition, optionsList, options) { if (!optionsList.length) { throw new RangeError("Options list should have at least one item."); } super(positionDefinition, Rectangle, options); this[selectedKey] = 0; const textOptions = { cursor: Component.cursors.pointer, fill: this.options.foreground, font: this.options.font, fontSize: this.options.fontSize, align: this.options.align, bold: this.options.bold, italic: this.options.italic, underscore: this.options.underscore, lineHeight: this.options.lineHeight, }; const margin = Text.measure("M", textOptions).height * Select.MARGIN; this.display = new Text(undefined, "", textOptions); this.optionsList = optionsList.map(option => new Text([(margin * 2) - 1, margin], option || "", textOptions)); const maxWidth = Math.max(...this.optionsList.map(text => text.width)); textOptions.origin = [maxWidth * this.display.getAlignOffset(), 0]; this.optionsContainer = new Rectangle(undefined, this.width, 0, { fill: this.options.fill, stroke: this.options.stroke, strokeWidth: this.options.strokeWidth, }); this.optionsContainer.hide(); let position = 0; this.optionsList.forEach((text, index) => { const rect = new Rectangle([1, position + 1], maxWidth + (6 * margin) - 2, text.height + (2 * margin), { fill: this.options.fill, cursor: Component.cursors.pointer, }); position += rect.height; let fill; rect .on(MouseEvent.events.hover, () => { fill = fill || rect.options.fill; rect.options.fill = this.options.hover; }) .on(MouseEvent.events.leave, () => { rect.options.fill = fill; fill = null; }) .on(MouseEvent.events.click, (event) => { event.stop(); this.value = index; this.fire(new BaseEvent(Select.events.change, this)); }); rect.add(text); this.optionsContainer.add(rect); }); this.optionsContainer.height = position + 2; this.arrow = new Triangle([maxWidth + (4 * margin), this.height / 2], margin, { fill: this.options.foreground, rotation: 0.5, cursor: Component.cursors.pointer, }); this.add(this.display, this.optionsContainer, this.arrow); } /** * Computer button size * @return {{width: Number, height: Number}} */ get size () { const measures = this.optionsList[this.value].getMeasures(); const maxWidth = Math.max(...this.optionsList.map(text => text.width)); const margin = Text.measure("M", this.optionsList[this.value].options).height * Select.MARGIN; return { width: maxWidth + (margin * 6), height: measures.height + (margin * 2), }; } /** * Get this button's width * @return {Number} */ get width () { return this.size.width; } /** * Get this button's height * @return {Number} */ get height () { return this.size.height; } /** * @return {Number} */ get value () { return this[selectedKey]; } /** * @param {Number} value - Index of the selected option */ set value (value) { this[selectedKey] = constrain(value, 0, this.optionsList.length - 1); this.display.text = this.optionsList[this.value].text; const margin = this.optionsList[0].height * Select.MARGIN; const origin = this.getOrigin(); this.display.options.origin.set(origin.clone().add(margin * 2, margin)); this.arrow.options.origin.set(origin.clone().multiply(-1)); this.optionsContainer.hide(); if (this.isHovered) { this.fire(new MouseEvent(MouseEvent.events.leave, this)); } } /** * @inheritDoc */ click () { const { position } = this.optionsList[this.value].parent; this.optionsContainer.position.set(this.getOrigin()).subtract(position).add(1, 0); this.optionsContainer.show(); } /** * @inheritDoc */ toJSON () { const json = super.toJSON(); json.values = this.optionsList.map(component => component.text); return json; } /** * @inheritDoc * @param {Object} definition - Select definition * @return {Select} */ static from (definition) { return new Select(definition.position, definition.values, definition.options); } /** * @typedef {Object} SelectOptions * @extends TextOptions * @extends InputOptions * @prop {String} [value=0] - Selected index of the select */ /** * @type {SelectOptions} */ static get defaultOptions () { return { ...Text.defaultOptions, ...super.defaultOptions, value: 0, }; } /** * Margin around the text * @type {Number} */ static get MARGIN () { return 0.2; } }