add: ui-switch style

add: virtual mode in Dropdown
This commit is contained in:
2024-05-27 17:06:00 +08:00
parent 190e43c814
commit ea7f4f538a
7 changed files with 277 additions and 139 deletions

View File

@ -48,4 +48,62 @@
color: var(--color);
}
}
}
.ui-switch {
position: relative;
line-height: 1rem;
user-select: none;
cursor: pointer;
>span:first-of-type {
display: inline-flex;
align-items: center;
&::before {
content: '';
width: 30px;
min-width: 30px;
max-width: 30px;
height: 16px;
margin-right: 4px;
background-color: var(--switch-bg-color);
border-radius: 8px;
transition: background-color .08s ease;
}
&::after {
content: '';
position: absolute;
left: 1px;
width: 14px;
height: 14px;
background-color: white;
border-radius: 7px;
box-shadow: 1px 1px 8px rgb(0 0 0 / 20%);
transition: left .08s ease;
}
}
>input[type="checkbox"] {
display: none;
&:checked+span:first-of-type {
&::before {
background-color: var(--switch-active-bg-color);
}
&::after {
left: 15px;
}
}
&:disabled+span:first-of-type {
&::before {
opacity: .5;
}
}
}
}

View File

@ -152,21 +152,25 @@ $listMaxHeight: 210px;
}
>.ui-drop-list {
margin: 0;
padding: 0;
list-style: none;
max-height: $listMaxHeight;
overflow-y: auto;
position: relative;
font-size: var(--font-size);
@include scrollbar();
&.filtered>li:first-child {
&.filtered>.drop-content>.li:first-child {
background-color: var(--hover-bg-color);
}
>li {
>.drop-content {
position: absolute;
width: 100%;
}
li {
// display: flex;
// align-items: center;
list-style: none;
line-height: $dropItemHeight;
height: $dropItemHeight;
padding: 0 10px;
@ -186,4 +190,4 @@ $listMaxHeight: 210px;
}
}
}
}
}

View File

@ -21,6 +21,8 @@
--disabled-color: #aaa;
--disabled-bg-color: #e9e9e9;
--disabled-border-color: #d9d9d9;
--switch-bg-color: #eae9eb;
--switch-active-bg-color: #33c559;
--red-color: red;
--title-color: #fff;

View File

@ -1,7 +1,7 @@
import './css/dropdown.scss';
import { r } from "../utility/lgres";
import { contains, nullOrEmpty } from "../utility/strings";
import { global, isPositive } from "../utility";
import { global, isPositive, throttle } from "../utility";
import { createElement } from "../functions";
import { createCheckbox } from "./checkbox";
import { createIcon } from "./icon"
@ -137,6 +137,9 @@ export class Dropdown {
// wrapper
const wrapper = createElement('div', 'ui-drop-wrapper');
const dropId = String(Math.random()).substring(2);
if (options.wrapper instanceof HTMLElement) {
options.wrapper.dataset.dropId = dropId;
}
wrapper.dataset.dropId = dropId;
dropdownGlobal[dropId] = this;
this._var.wrapper = wrapper;
@ -241,6 +244,8 @@ export class Dropdown {
get multiSelect() { return this._var.options.multiSelect }
get ignoreAll() { return this._var.options.ignoreAll }
get disabled() { return this._var.wrapper == null || this._var.wrapper.querySelector('.ui-drop-header.disabled') != null }
set disabled(flag) {
@ -363,7 +368,7 @@ export class Dropdown {
});
if (itemlist.length === 0) {
this._var.selectedList = null;
this._var.label.innerText = none;
this._var.label.innerText = r('none', '( None )');
return false;
}
selectItems(this._var.label, itemlist, htmlkey, textkey);
@ -396,7 +401,8 @@ export class Dropdown {
panel.appendChild(search);
}
// list
const list = createElement('ul', 'ui-drop-list');
const list = createElement('div', 'ui-drop-list');
list.addEventListener('scroll', e => throttle(this._onlistscroll, 10, this, list, e.target.scrollTop), { passive: true });
if (!this.multiSelect) {
list.addEventListener('click', e => {
let li = e.target;
@ -468,36 +474,99 @@ export class Dropdown {
}
}
panel.classList.add('active');
this._var.dropTop = 0;
panel.querySelector('.ui-drop-list').dispatchEvent(new Event('scroll'));
} else {
panel.classList.remove('active');
}
}
_onlistscroll(list, top) {
const offset = (this.multiSelect && !this.ignoreAll) ? DropdownItemHeight : 0;
top -= (top % (DropdownItemHeight * 2)) + offset;
if (top < 0) {
top = 0;
} else {
let bottomTop = this._var.dropHeight - (20 * DropdownItemHeight);
if (bottomTop < 0) {
bottomTop = 0;
}
if (top > bottomTop) {
top = bottomTop;
}
}
if (this._var.dropTop !== top) {
this._var.dropTop = top;
const startIndex = top / DropdownItemHeight;
let array = this._var.currentSource;
if (startIndex + 20 < array.length) {
array = array.slice(startIndex, startIndex + 20);
} else {
array = array.slice(-20);
}
const content = list.querySelector('.drop-content');
content.replaceChildren();
this._dofilllist(content, array);
content.style.top = `${top + offset}px`;
}
}
_filllist(source) {
const list = this._var.container.querySelector('.ui-drop-list');
list.replaceChildren();
const multiselect = this.multiSelect;
const allchecked = this._var.allChecked;
if (multiselect) {
const height = source.length * DropdownItemHeight;
this._var.dropHeight = height;
this._var.currentSource = source;
const holder = createElement('div', 'drop-holder');
holder.style.height = `${height}px`;
const content = createElement('div', 'drop-content');
if (this.multiSelect && !this.ignoreAll) {
list.appendChild(
createElement('li', null,
createCheckbox({
label: r('allItem', '( All )'),
checked: allchecked,
checked: this._var.allChecked,
customAttributes: { 'isall': '1' },
onchange: e => this._triggerselect(e.target)
})
)
);
content.style.top = `${DropdownItemHeight}px`;
} else {
content.style.top = '0px';
}
// TODO: virtual mode
const multiselect = this.multiSelect;
const valuekey = this._var.options.valueKey;
const allchecked = this._var.allChecked;
const selectedlist = this.selectedList;
source.forEach((item, i) => {
let val = item[valuekey];
if (typeof val !== 'string') {
val = String(val);
}
if (multiselect) {
const selected = selectedlist.some(s => String(s[valuekey]) === val);
item.__checked = allchecked || selected;
}
});
if (source.length > 20) {
source = source.slice(0, 20);
}
const scrolled = this._dofilllist(content, source);
list.append(holder, content);
if (scrolled != null) {
setTimeout(() => list.scrollTop = scrolled, 10);
}
}
_dofilllist(content, array) {
const multiselect = this.multiSelect;
const valuekey = this._var.options.valueKey;
const textkey = this._var.options.textKey;
const htmlkey = this._var.options.htmlKey;
const selected = this.selected;
const selectedlist = this.selectedList;
let scrolled;
source.slice(0, 200).forEach((item, i) => {
array.forEach((item, i) => {
let val = item[valuekey];
if (typeof val !== 'string') {
val = String(val);
@ -514,19 +583,18 @@ export class Dropdown {
label.innerHTML = html;
}
if (multiselect) {
const selected = selectedlist.some(s => String(s[valuekey]) === val);
if (label == null) {
label = createElement('span');
label.innerText = item[textkey];
}
const box = createCheckbox({
label,
checked: allchecked || selected,
checked: item.__checked,
customAttributes: {
'class': 'dataitem',
'data-value': val
},
onchange: e => this._triggerselect(e.target)
onchange: e => this._triggerselect(e.target, item)
});
li.appendChild(box);
} else {
@ -540,14 +608,12 @@ export class Dropdown {
li.classList.add('selected');
}
}
list.appendChild(li);
content.appendChild(li);
});
if (scrolled != null) {
setTimeout(() => list.scrollTop = scrolled, 10);
}
return scrolled;
}
_triggerselect(checkbox) {
_triggerselect(checkbox, item) {
let list;
const valuekey = this._var.options.valueKey;
const textkey = this._var.options.textKey;
@ -557,28 +623,31 @@ export class Dropdown {
const boxes = this._var.container.querySelectorAll('input.dataitem');
boxes.forEach(box => box.checked = allchecked);
list = [];
} else if (checkbox.checked) {
if (this._var.container.querySelectorAll('input.dataitem:not(:checked)').length === 0) {
this._var.allChecked = true;
this._var.container.querySelector('input[isall="1"]').checked = true;
list = [];
} else {
const source = this.source;
list = [...this._var.container.querySelectorAll('input.dataitem:checked')]
.map(c => {
const v = c.dataset.value;
return source.find(it => String(it[valuekey]) === v);
})
.filter(it => it != null);
}
} else {
const val = checkbox.dataset.value;
if (this._var.allChecked) {
this._var.allChecked = false;
this._var.container.querySelector('input[isall="1"]').checked = false;
list = this.source.filter(it => String(it[valuekey]) !== val);
item.__checked = checkbox.checked;
const all = this._var.container.querySelector('input[isall="1"]');
if (checkbox.checked) {
const source = this.source;
if (source.some(it => it.__checked) == null) {
this._var.allChecked = true;
if (all != null) {
all.checked = true;
}
list = [];
} else {
list = source.filter(it => it.__checked);
}
} else {
list = this.selectedList.filter(it => String(it[valuekey]) !== val);
const val = checkbox.dataset.value;
if (this._var.allChecked) {
this._var.allChecked = false;
if (all != null) {
all.checked = false;
}
list = this.source.filter(it => String(it[valuekey]) !== val);
} else {
list = this.selectedList.filter(it => String(it[valuekey]) !== val);
}
}
}
if (this._var.allChecked) {