monphind-ui
Version:
A reactive component library built on top of the Web Components API
207 lines (206 loc) • 7.51 kB
JavaScript
import { useElement } from "./core/element";
import debounce from "./core/debounce";
import { select } from "./core/select-elememt";
const name = 'm-segmented-button';
const style = `:host {
width: -moz-fit-content;
display:block;
width: fit-content;
border-radius: 10000000px;
padding: 5px;
position: relative;
background-color: var(--m-segmented-button-backgroundColor,${"white"});
}
:host .selected-slider {
position: absolute;
top: 0;
left: 5px;
bottom: 0;
margin: auto;
height: -moz-fit-content;
height: fit-content;
padding: 10px 0;
font-size: 14px;
border-radius: 10000000px;
z-index: 1;
background-color: var(--m-segmented-button-slider-backgroundColor,${"#2EA2F9"});
color: white;
transition: left 0.3s, box-shadow 0.3s, transform 0.2s, width 0.3s;
cursor: pointer;
text-align:center;
white-space:nowrap;
display:none;
}
:host .selected-slider:hover {
box-shadow: 0px 4px 20px var(--m-segmented-button-slider-hover-boxShadowColor,${"#2ea1f9d6"});
}
:host .selected-slider:active {
transform: scale(0.96);
}
:host slot {
display: flex;
width: -moz-fit-content;
width: 100%;
}
`;
const template = `
<div class="selected-slider"></div>
<slot></slot>`;
const props = {
value: "",
index: 0
};
export class SegmentedButton extends useElement({
name, style, template, syncProps: ["value"], props, dispatch: {
propChanged(key, value) {
if (key === 'value') {
const allItems = this.querySelectorAll("m-segmented-button-item");
for (const i in allItems) {
if (allItems[i].value == value) {
this.index = Number(i);
break;
}
}
const { left, width } = allItems[this.index].getBoundingClientRect();
const { offsetLeft } = this;
const slider = this.shadowRoot?.querySelector(".selected-slider");
slider.style.left = `${left - offsetLeft}px`;
slider.style.width = `${width}px`;
slider.innerHTML = allItems[this.index].innerHTML;
}
},
connected() {
let totalLeft = this.offsetLeft;
let totalWidth = this.getBoundingClientRect().width;
const slider = this.shadowRoot?.querySelector(".selected-slider");
const defaultTransition = getComputedStyle(slider).transition;
const selected = select(this, "m-segmented-button-item", { selected: true });
if (selected[0]) {
const allItems = this.querySelectorAll("m-segmented-button-item");
for (let i = 0; i < allItems.length; i++) {
if (allItems[i] === selected[0]) {
this.index = i;
break;
}
}
const html = selected[0].innerHTML;
const value = selected[0].value || "";
const { width, left } = selected[0].getBoundingClientRect();
const realLeft = left - totalLeft;
slider.style.left = `${realLeft}px`;
slider.style.width = `${width}px`;
slider.innerHTML = html;
this.value = value;
}
else {
let usedIndex = 0;
const allItems = this.querySelectorAll("m-segmented-button-item");
if (allItems[usedIndex].disabled === true) {
usedIndex = NaN;
for (const i in allItems) {
if (allItems[i].disabled === false) {
usedIndex = Number(i);
break;
}
else
continue;
}
}
if (usedIndex.toString() === 'NaN')
throw new Error("The default selection is misinitialized. Be sure to have at least one non-disabled element in the group list.");
const html = allItems[usedIndex].innerHTML;
const value = allItems[usedIndex].value || "";
const { width, left } = allItems[usedIndex].getBoundingClientRect();
slider.style.left = `${left - totalLeft}px`;
slider.style.width = `${width}px`;
slider.innerHTML = html;
this.value = value;
this.index = usedIndex;
}
const totalItems = this.querySelectorAll("m-segmented-button-item");
for (let i = 0; i < totalItems.length; i++) {
totalItems[i].addEventListener("click", () => {
if (i === this.index)
return;
this.index = i;
const { value, innerHTML } = totalItems[i];
const { width, left } = totalItems[i].getBoundingClientRect();
slider.style.width = `${width}px`;
slider.style.left = `${left - totalLeft}px`;
this.value = value;
slider.innerHTML = innerHTML;
this.dispatchEvent(new Event('change'));
});
}
const sliderVar = debounce((defaultTransition, slider) => {
slider.style.transition = defaultTransition;
}, 300);
const robserver = new ResizeObserver((entries) => {
const newWidth = entries[0].contentRect.width;
if (newWidth != totalWidth) {
totalWidth = newWidth;
totalLeft = this.offsetLeft;
const selected = {
newWidth: totalItems[this.index].getBoundingClientRect().width,
newLeft: totalItems[this.index].getBoundingClientRect().left
};
slider.style.transition = 'none';
slider.style.width = `${selected.newWidth}px`;
slider.style.left = `${selected.newLeft - totalLeft}px`;
sliderVar(defaultTransition, slider);
}
else
return;
});
robserver.observe(this);
slider.style.display = 'initial';
}
},
setup() {
return {};
}
}) {
}
SegmentedButton.defineElement();
const itemName = 'm-segmented-button-item';
const itemStyle = `:host {
padding: 10px 20px;
font-size: 14px;
border-radius: 10000000px;
cursor: pointer;
transition: background-color 0.3s;
width:100%;
text-align:center;
display:flex;
justify-content:center;
align-items:center;
}
:host slot{
white-space:nowrap;
}
:host(:hover) {
background-color: var(--m-segmented-button-item-hover-backgroundColor,${"#F2F2F2"});
}
:host(:active) {
background-color: var(--m-segmented-button-item-active-backgroundColor,${"#e2e2e2"});
}
:host([disabled=true]){
pointer-events:none;
color:gray;
}`;
const itemTemplate = `<slot></slot>`;
const itemProps = {
selected: false,
value: "",
disabled: false
};
export class SegmentedButtonItem extends useElement({
name: itemName,
props: itemProps,
style: itemStyle,
template: itemTemplate,
syncProps: ["disabled"],
}) {
}
SegmentedButtonItem.defineElement();
import 'vue';