@v4fire/client
Version:
V4Fire client core library
386 lines (307 loc) • 8.94 kB
text/typescript
/* eslint-disable @typescript-eslint/no-unused-vars-experimental */
/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/
/**
* [[include:traits/i-active-items/README.md]]
* @packageDocumentation
*/
import type iBlock from 'super/i-block/i-block';
import iItems from 'traits/i-items/i-items';
import type { Active, Item } from 'traits/i-active-items/interface';
export * from 'traits/i-items/i-items';
export * from 'traits/i-active-items/interface';
type TraitComponent = iBlock & iActiveItems;
export default abstract class iActiveItems extends iItems {
/** @see [[iItems.Item]] */
abstract override readonly Item: Item;
/**
* Type: the component active item
*/
abstract readonly Active: Active;
/**
* The active item(s) of the component.
* If the component is switched to "multiple" mode, you can pass in an array to define multiple active items.
*
* @prop
*/
abstract readonly activeProp?: unknown[] | this['Active'];
/**
* If true, the component supports the multiple active items feature
* @prop
*/
abstract readonly multiple: boolean;
/**
* If set to true, the active item can be canceled by clicking it again.
* By default, if the component is switched to the `multiple` mode, this value is set to `true`,
* otherwise it is set to `false`.
*
* @prop
*/
abstract readonly cancelable?: boolean;
/**
* The active item(s) of the component.
* If the component is switched to "multiple" mode, the getter will return a Set.
*
* @see [[iActiveItems.prototype.activeStore]]
*/
abstract get active(): this['Active'];
/**
* Store for the active item(s) of the component
* @see [[iActiveItems.activeProp]]
*/
abstract activeStore: this['Active'];
/**
* Link(s) to the DOM element of the component active item.
* If the component is switched to the `multiple` mode, the getter will return a list of elements.
*/
abstract get activeElement(): CanPromise<CanArray<Element> | null>;
/**
* Returns a `sync.link` to `activeProp` for `activeStore`
* @emits `immediateChange(active: CanArray<unknown>)`
*/
static linkActiveStore(ctx: TraitComponent): iActiveItems['Active'] {
return ctx.sync.link('activeProp', (val: iActiveItems['Active']) => {
const
beforeDataCreate = ctx.hook === 'beforeDataCreate';
if (val === undefined && beforeDataCreate) {
if (ctx.multiple) {
if (Object.isSet(ctx.activeStore)) {
return ctx.activeStore;
}
return new Set(Array.concat([], ctx.activeStore));
}
return ctx.activeStore;
}
let
newVal;
if (ctx.multiple) {
newVal = new Set(Object.isSet(val) ? val : Array.concat([], val));
if (Object.fastCompare(newVal, ctx.activeStore)) {
return ctx.activeStore;
}
} else {
newVal = val;
}
if (beforeDataCreate) {
ctx.emit('immediateChange', ctx.multiple ? new Set(newVal) : newVal);
} else {
ctx.setActive(newVal);
}
return newVal;
});
}
/**
* Checks if the passed item has an active property value.
* If true, sets it as the component active value.
*
* @param ctx
* @param item
*/
static initItem(ctx: TraitComponent, item: Item): void {
if (item.active && (ctx.multiple ? ctx.activeProp === undefined : ctx.active === undefined)) {
ctx.setActive(item.value);
}
}
/**
* Returns the active item(s) of the passed component
*/
static getActive(ctx: TraitComponent): iActiveItems['Active'] {
const
v = ctx.field.get<iActiveItems['Active']>('activeStore');
if (ctx.multiple) {
return Object.isSet(v) ? new Set(v) : new Set();
}
return v;
}
/** @see [[iActiveItems.prototype.isActive]] */
static isActive: AddSelf<iActiveItems['isActive'], TraitComponent> = (ctx, value: Item['value']) => {
const
{active} = ctx;
if (ctx.multiple) {
if (!Object.isSet(active)) {
return false;
}
return active.has(value);
}
return value === active;
};
/** @see [[iActiveItems.prototype.setActive]] */
static setActive(ctx: TraitComponent, value: iActiveItems['Active'], unsetPrevious?: boolean): boolean {
if (!this.isActivatable(ctx, value)) {
return false;
}
const
activeStore = ctx.field.get('activeStore');
if (ctx.multiple) {
if (!Object.isSet(activeStore)) {
return false;
}
if (unsetPrevious) {
ctx.field.set('activeStore', new Set());
}
let
res = false;
const set = (value) => {
if (activeStore.has(value)) {
return;
}
activeStore.add(value);
res = true;
};
if (Object.isSet(value)) {
Object.forEach(value, set);
} else {
set(value);
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!res) {
return false;
}
} else if (activeStore === value) {
return false;
} else {
ctx.field.set('activeStore', value);
}
ctx.emit('immediateChange', ctx.active);
ctx.emit('change', ctx.active);
return true;
}
/** @see [[iActiveItems.prototype.unsetActive]] */
static unsetActive(ctx: TraitComponent, value: iActiveItems['Active']): boolean {
const
activeStore = ctx.field.get('activeStore');
if (ctx.multiple) {
if (!Object.isSet(activeStore)) {
return false;
}
let
res = false;
const unset = (value) => {
if (!activeStore.has(value) || ctx.cancelable === false) {
return false;
}
activeStore.delete(value);
res = true;
};
if (Object.isSet(value)) {
Object.forEach(value, unset);
} else {
unset(value);
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!res) {
return false;
}
} else if (activeStore !== value || ctx.cancelable !== true) {
return false;
} else {
ctx.field.set('activeStore', undefined);
}
ctx.emit('immediateChange', ctx.active);
ctx.emit('change', ctx.active);
return true;
}
/** @see [[iActiveItems.prototype.toggleActive]] */
static toggleActive(ctx: TraitComponent, value: iActiveItems['Active'], unsetPrevious?: boolean): iActiveItems['Active'] {
if (!this.isActivatable(ctx, value)) {
return false;
}
const
activeStore = ctx.field.get('activeStore');
if (ctx.multiple) {
if (!Object.isSet(activeStore)) {
return ctx.active;
}
const toggle = (value) => {
if (activeStore.has(value)) {
ctx.unsetActive(value);
return;
}
ctx.setActive(value);
};
if (Object.isSet(value)) {
if (unsetPrevious) {
ctx.unsetActive(ctx.active);
}
Object.forEach(value, toggle);
} else {
toggle(value);
}
} else if (activeStore !== value) {
ctx.setActive(value);
} else {
ctx.unsetActive(value);
}
return ctx.active;
}
/** @see [[iActiveItems.prototype.getItemByValue]] */
static getItemByValue(ctx: TraitComponent, value: Item['value']): CanUndef<Item> {
return ctx.items?.find((item) => item.value === value);
}
/**
* Checks if item can possibly be active by its value
*
* @param ctx
* @param value
*/
protected static isActivatable(ctx: TraitComponent, value: Item['value']): boolean {
const item = ctx.getItemByValue(value);
return item?.activatable !== false;
}
/**
* Returns true if the specified value is active
* @param value
*/
isActive(value: Item['value']): boolean {
return Object.throw();
}
/**
* Activates the item(s) by the specified value(s).
* If the component is switched to the `multiple` mode, the method can take a Set to set multiple items.
*
* @param value
* @param [unsetPrevious] - true, if needed to reset previous active items (only works in the `multiple` mode)
*
* @emits `change(active: CanArray<unknown>)`
* @emits `immediateChange(active: CanArray<unknown>)`
*/
setActive(value: Item['value'] | Set<Item['value']>, unsetPrevious?: boolean): boolean {
return Object.throw();
}
/**
* Deactivates the item(s) by the specified value(s).
* If the component is switched to the `multiple` mode, the method can take a Set to unset multiple items.
* @param value
* @emits `change(active: unknown)`
* @emits `immediateChange(active: unknown)`
*/
unsetActive(value: Item['value'] | Set<Item['value']>): boolean {
return Object.throw();
}
/**
* Toggles item activation by the specified value.
* The methods return the new active component item(s).
*
* @param value
* @param [unsetPrevious] - true, if needed to reset previous active items (only works in the `multiple` mode)
*
* @emits `change(active: unknown)`
* @emits `immediateChange(active: unknown)`
*/
toggleActive(value: Item['value'], unsetPrevious?: boolean): iActiveItems['Active'] {
return Object.throw();
}
/**
* Returns an item by the specified value
*
* @param value
*/
getItemByValue(value: Item['value']): CanUndef<Item> {
return Object.throw();
}
}