UNPKG

@salla.sa/twilight-components

Version:
264 lines (260 loc) 10.2 kB
/*! * Crafted with ❤ by Salla */ import { proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client'; import { A as ArrowDown } from './keyboard_arrow_down.js'; const sallaColorPickerCss = ""; const SallaColorPicker = /*@__PURE__*/ proxyCustomElement(class SallaColorPicker extends HTMLElement { constructor() { super(); this.__registerHost(); this.colorChanged = createEvent(this, "colorChanged", 7); this.invalidInput = createEvent(this, "invalidInput", 7); this.submitted = createEvent(this, "submitted", 7); this.popupOpened = createEvent(this, "popupOpened", 7); this.popupClosed = createEvent(this, "popupClosed", 7); /** Lazy-loaded to avoid bundling vanilla-picker (Babel polyfills) into main/hydrate bundle. */ this.picker = null; this.pickerReady = null; /** * File input name for the native formData */ this.name = 'color'; /** * Set if the color picker input is required or not */ this.required = false; /** * How to display the selected color in the text field * (the text field still supports input in any format). */ this.format = 'hex'; /** * Whether to have a "Cancel" button which closes the popup. */ this.showCancelButton = false; /** * Whether to show a text field for color value editing. */ this.showTextField = true; /** * Whether to enable adjusting the alpha channel. */ this.enableAlpha = false; this.updateViewportCache = () => { this.cachedViewportWidth = this.getViewportWidthFromBreakpoints(); }; this.cachedViewportWidth = 0; // Will be populated in componentDidLoad via requestAnimationFrame } colorChangeHandler(color) { if (this.colorInput) this.colorInput.value = color.hex; this.colorChanged.emit(color); } async submittedHandler(color) { await this.setColorValue(color.rgbaString, true); if (this.canvas) this.canvas.style.backgroundColor = color.rgbString; if (this.colorInput) { this.colorInput.value = color.hex; this.colorInput.dispatchEvent(new window.Event('change', { bubbles: true })); } this.submitted.emit(color); } popupOpenedHandler(color) { // Double rAF: defer setPopInPosition (getBoundingClientRect) until after paint to avoid forced reflow requestAnimationFrame(() => { requestAnimationFrame(() => this.setPopInPosition()); }); this.popupOpened.emit(color); } popupClosedHandler(color) { this.popupClosed.emit(color); } /** Methods */ async ensurePicker() { if (this.picker) return this.picker; if (this.pickerReady) return await this.pickerReady; throw new Error('Color picker not initialized'); } /** * Set the picker options. * * (Usually a new .parent and .color). * @param {Object} options */ async setPickerOption(options) { const picker = await this.ensurePicker(); picker.setOptions(options); } /** * Move the popup to a different parent, optionally opening it at the same time. * * (Usually a new .parent and .color). * @param {Options} option * * Whether to open the popup immediately. * @param {boolean} openImmediately */ async movePopUp(options, openImmediately) { const picker = await this.ensurePicker(); picker.movePopup(options, openImmediately); } /** * Set/initialize the picker's color. * * Color name, RGBA/HSLA/HEX string, or RGBA array. * @param {string} color * * If true, won't trigger onChange. * @param {boolean} triggerEvent */ async setColorValue(color, triggerEvent) { const picker = await this.ensurePicker(); picker.setColor(color, triggerEvent); } /** * Show/open the picker. */ async openPicker() { const picker = await this.ensurePicker(); picker.show(); } /** * Close/Hide the picker. */ async closePicker() { const picker = await this.ensurePicker(); picker.hide(); } /** * Release all resources used by this picker instance. */ async destroyPicker() { const picker = await this.ensurePicker(); picker.destroy(); this.picker = null; this.pickerReady = null; } disconnectedCallback() { window.removeEventListener('resize', this.updateViewportCache); } componentWillLoad() { salla.onReady(() => { this.color = this.color ? this.color : salla.config.get('theme.color.primary', '#5dd5c4'); }); } /** * Returns viewport width from matchMedia breakpoints (no layout read). * Must match updateViewportCache breakpoints for consistency. */ getViewportWidthFromBreakpoints() { if (typeof window === 'undefined') return 1024; if (window.matchMedia('(min-width: 1200px)').matches) return 1200; if (window.matchMedia('(min-width: 992px)').matches) return 992; if (window.matchMedia('(min-width: 768px)').matches) return 768; return 375; } setPopInPosition() { // Use cached viewport width – reading innerWidth at popup open causes forced reflow. // Fallback: matchMedia-based value when cache is 0 (e.g. SSR before hydration). Same breakpoints as updateViewportCache. const viewportWidth = this.cachedViewportWidth || this.getViewportWidthFromBreakpoints(); const popup = this.host.querySelector('.picker_wrapper'); const widgetEl = this.host.querySelector('.s-color-picker-widget'); if (!popup || !widgetEl) return; const widgetPosition = widgetEl.getBoundingClientRect(); const widgetToWindowEq = viewportWidth / 2 - widgetPosition.width / 2; const widgetInLeft = widgetToWindowEq > widgetPosition.x; const widgetInRight = widgetToWindowEq < widgetPosition.x; const widgetInCenter = widgetToWindowEq === widgetPosition.x; const isMobile = !window.matchMedia('(min-width: 768px)').matches; if (isMobile && widgetInLeft) { popup.style.left = '0'; } if (isMobile && widgetInRight) { popup.style.left = 'auto'; } if (!isMobile || (isMobile && ((!widgetInRight && !widgetInLeft) || widgetInCenter))) { popup.style.left = `-95px`; } } initColorPicker() { this.pickerReady = import('./vanilla-picker.js').then(function (n) { return n.v; }).then(mod => { const Picker = mod.default; const picker = new Picker({ parent: this.host, color: this.color, popup: 'bottom', alpha: this.enableAlpha, editor: this.showTextField, editorFormat: this.format, cancelButton: this.showCancelButton, onChange: (color) => this.colorChangeHandler(color), onDone: (color) => this.submittedHandler(color), onOpen: (color) => this.popupOpenedHandler(color), onClose: (color) => this.popupClosedHandler(color), }); this.picker = picker; return this.picker; }); } render() { return (h(Host, { key: '2f142fd64afd5571574a7e1c40de3be16ede62e6', class: "s-color-picker-main" }, h("slot", { key: '92acdf14b2f42ac52d0a632ba6840a5dcebfa4d4', name: "widget" }, h("div", { key: '7ba074040299a91802109d0e1d2e78604e39d3a8', class: "s-color-picker-widget" }, h("div", { key: 'a6272207f79fd28e33b66e415a3e05b969ace983', class: "s-color-picker-widget-canvas", ref: dv => (this.canvas = dv) }), h("span", { key: 'e6fed242bb84fae6221cdec525658b68f7514ce7', innerHTML: ArrowDown }))), h("input", { key: '9eb1673f44e663f939124da45fec79c401736203', class: "s-hidden", name: this.name, required: this.required, value: this.color, ref: color => (this.colorInput = color) }))); } componentDidLoad() { if (this.canvas) this.canvas.style.backgroundColor = this.color; this.initColorPicker(); // Populate viewport cache in next frame (avoids forced reflow during initial render) requestAnimationFrame(() => this.updateViewportCache()); window.addEventListener('resize', this.updateViewportCache); if (this.colorInput) { this.colorInput.addEventListener('invalid', e => { this.invalidInput.emit(e); }); this.colorInput.addEventListener('input', () => { this.colorInput.setCustomValidity(''); this.colorInput.reportValidity(); }); } } get host() { return this; } static get style() { return sallaColorPickerCss; } }, [4, "salla-color-picker", { "name": [1], "required": [4], "color": [1537], "format": [1], "showCancelButton": [4, "show-cancel-button"], "showTextField": [4, "show-text-field"], "enableAlpha": [4, "enable-alpha"], "widgetColor": [32], "setPickerOption": [64], "movePopUp": [64], "setColorValue": [64], "openPicker": [64], "closePicker": [64], "destroyPicker": [64] }]); function defineCustomElement() { if (typeof customElements === "undefined") { return; } const components = ["salla-color-picker"]; components.forEach(tagName => { switch (tagName) { case "salla-color-picker": if (!customElements.get(tagName)) { customElements.define(tagName, SallaColorPicker); } break; } }); } defineCustomElement(); export { SallaColorPicker as S, defineCustomElement as d };