@salla.sa/twilight-components
Version:
Salla Web Component
264 lines (260 loc) • 10.2 kB
JavaScript
/*!
* 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 };