tweb/src/components/checkboxFields.ts

158 lines
5.1 KiB
TypeScript

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import cancelEvent from '../helpers/dom/cancelEvent';
import {attachClickEvent} from '../helpers/dom/clickEvent';
import findUpAsChild from '../helpers/dom/findUpAsChild';
import ListenerSetter from '../helpers/listenerSetter';
import safeAssign from '../helpers/object/safeAssign';
import I18n, {LangPackKey} from '../lib/langPack';
import CheckboxField from './checkboxField';
import Row from './row';
import {toast} from './toast';
export type CheckboxFieldsField = {
text: LangPackKey,
description?: LangPackKey,
restrictionText?: LangPackKey,
checkboxField?: CheckboxField,
checked?: boolean,
nested?: CheckboxFieldsField[],
nestedTo?: CheckboxFieldsField,
nestedCounter?: HTMLElement,
setNestedCounter?: (count: number) => void,
toggleWith?: {checked?: CheckboxFieldsField[], unchecked?: CheckboxFieldsField[]},
name?: string,
row?: Row
};
export default class CheckboxFields<K extends CheckboxFieldsField = CheckboxFieldsField> {
public fields: Array<K>;
protected listenerSetter: ListenerSetter;
protected asRestrictions: boolean;
constructor(options: {
fields: Array<K>,
listenerSetter: ListenerSetter,
asRestrictions?: boolean
}) {
safeAssign(this, options);
}
public createField(info: CheckboxFieldsField, isNested?: boolean) {
if(info.nestedTo && !isNested) {
return;
}
const row = info.row = new Row({
titleLangKey: isNested ? undefined : info.text,
checkboxField: info.checkboxField = new CheckboxField({
text: isNested ? info.text : undefined,
checked: info.nested ? false : info.checked,
toggle: !isNested,
listenerSetter: this.listenerSetter,
restriction: this.asRestrictions && !isNested,
name: info.name
}),
listenerSetter: this.listenerSetter,
subtitleLangKey: info.description,
clickable: info.nested ? (e) => {
if(findUpAsChild(e.target as HTMLElement, row.checkboxField.label)) {
return;
}
cancelEvent(e);
row.container.classList.toggle('accordion-toggler-expanded');
accordion.classList.toggle('is-expanded');
} : undefined
});
if(info.restrictionText) {
info.checkboxField.input.disabled = true;
attachClickEvent(info.checkboxField.label, (e) => {
toast(I18n.format(info.restrictionText, true));
}, {listenerSetter: this.listenerSetter});
}
const nodes: HTMLElement[] = [row.container];
let accordion: HTMLElement, nestedCounter: HTMLElement;
if(info.nested) {
const container = accordion = document.createElement('div');
container.classList.add('accordion');
container.style.setProperty('--max-height', info.nested.length * 48 + 'px');
const _info = info;
info.nested.forEach((info) => {
info.nestedTo ??= _info;
container.append(...this.createField(info, true).nodes);
});
nodes.push(container);
const span = document.createElement('span');
span.classList.add('tgico-down', 'accordion-icon');
nestedCounter = info.nestedCounter = document.createElement('b');
this.setNestedCounter(info);
row.title.append(' ', nestedCounter, ' ', span);
row.container.classList.add('accordion-toggler');
row.titleRow.classList.add('with-delimiter');
row.checkboxField.setValueSilently(this.getNestedCheckedLength(info) === info.nested.length);
info.toggleWith ??= {checked: info.nested, unchecked: info.nested};
}
if(info.toggleWith || info.nestedTo) {
const processToggleWith = info.toggleWith ? (info: CheckboxFieldsField) => {
const {toggleWith, nested} = info;
const value = info.checkboxField.checked;
const arr = value ? toggleWith.checked : toggleWith.unchecked;
if(!arr) {
return;
}
const other = this.fields.filter((i) => arr.includes(i));
other.forEach((info) => {
info.checkboxField.setValueSilently(value);
if(info.nestedTo && !nested) {
this.setNestedCounter(info.nestedTo);
}
if(info.toggleWith) {
processToggleWith(info);
}
});
if(info.nested) {
this.setNestedCounter(info);
}
} : undefined;
const processNestedTo = info.nestedTo ? () => {
const length = this.getNestedCheckedLength(info.nestedTo);
info.nestedTo.checkboxField.setValueSilently(length === info.nestedTo.nested.length);
this.setNestedCounter(info.nestedTo, length);
} : undefined;
this.listenerSetter.add(info.checkboxField.input)('change', () => {
processToggleWith?.(info);
processNestedTo?.();
});
}
return {row, nodes};
}
protected getNestedCheckedLength(info: CheckboxFieldsField) {
return info.nested.reduce((acc, v) => acc + +v.checkboxField.checked, 0);
}
public setNestedCounter(info: CheckboxFieldsField, count = this.getNestedCheckedLength(info)) {
info.nestedCounter.textContent = `${count}/${info.nested.length}`;
}
}