UNPKG

@v4fire/client

Version:

V4Fire client core library

386 lines (307 loc) • 8.94 kB
/* 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(); } }