@v4fire/client
Version:
V4Fire client core library
177 lines (145 loc) • 4 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-open/README.md]]
* @packageDocumentation
*/
import SyncPromise from 'core/promise/sync';
import type iBlock from 'super/i-block/i-block';
import type { ModsDecl, ModEvent, SetModEvent } from 'super/i-block/i-block';
import type { CloseHelperEvents } from 'traits/i-open/interface';
export * from 'traits/i-open/interface';
export default abstract class iOpen {
/**
* Trait modifiers
*/
static readonly mods: ModsDecl = {
opened: [
'true',
'false'
]
};
/** @see [[iOpen.open]] */
static open: AddSelf<iOpen['open'], iBlock> =
(component) => SyncPromise.resolve(component.setMod('opened', true));
/** @see [[iOpen.close]] */
static close: AddSelf<iOpen['close'], iBlock> =
(component) => SyncPromise.resolve(component.setMod('opened', false));
/** @see [[iOpen.onOpenedChange]] */
static onOpenedChange: AddSelf<iOpen['onOpenedChange'], iBlock> = async (component) => {
// Loopback
};
/** @see [[iOpen.onKeyClose]] */
static onKeyClose: AddSelf<iOpen['onKeyClose'], iBlock & iOpen> = async (component, e) => {
if (e.key === 'Escape') {
await component.close();
}
};
/** @see [[iOpen.onTouchClose]] */
static onTouchClose: AddSelf<iOpen['onTouchClose'], iBlock & iOpen> = async (component, e) => {
const
target = <CanUndef<Element>>e.target;
if (target == null) {
return;
}
if (!target.closest(`.${component.componentId}`)) {
await component.close();
}
};
/**
* Initialize default event listeners to close a component by a keyboard or mouse
*
* @param component
* @param [events] - map with events to listen
*/
static initCloseHelpers<T extends iBlock>(component: T & iOpen, events: CloseHelperEvents = {}): void {
const
{async: $a, localEmitter: $e} = component.unsafe;
const
helpersGroup = {group: 'closeHelpers'},
modsGroup = {group: 'closeHelpers:mods'};
$a.off({group: /closeHelpers/});
$e.on('block.mod.*.opened.*', component.onOpenedChange.bind(component), modsGroup);
$e.on('block.mod.set.opened.false', () => $a.off(helpersGroup), modsGroup);
const onOpened = () => {
$a.setTimeout(() => {
const opts = {
...helpersGroup,
options: {passive: false}
};
try {
$a.on(document, events.key ?? 'keyup', (e) => {
if (e != null) {
return component.onKeyClose(e);
}
}, opts);
$a.on(document, events.touch ?? 'click touchend', (e) => {
if (e != null) {
return component.onTouchClose(e);
}
}, opts);
} catch {}
}, 0, helpersGroup);
};
$e.on('block.mod.set.opened.true', onOpened, modsGroup);
}
/**
* Initializes modifier event listeners
*
* @emits `open()`
* @emits `close()`
*
* @param component
*/
static initModEvents<T extends iBlock>(component: T): void {
const
{localEmitter: $e} = component.unsafe;
$e.on('block.mod.*.opened.*', (e: ModEvent) => {
if (e.type === 'remove' && e.reason !== 'removeMod') {
return;
}
component.emit(e.value === 'false' || e.type === 'remove' ? 'close' : 'open');
});
}
/**
* Opens the component
* @param args
*/
open(...args: unknown[]): Promise<boolean> {
return Object.throw();
}
/**
* Closes the component
* @param args
*/
close(...args: unknown[]): Promise<boolean> {
return Object.throw();
}
/**
* Handler: the opened modifier has been changed
* @param e
*/
onOpenedChange(e: ModEvent | SetModEvent): Promise<void> {
return Object.throw();
}
/**
* Handler: closing by a keyboard event
* @param e
*/
onKeyClose(e: KeyboardEvent): Promise<void> {
return Object.throw();
}
/**
* Handler: closing by a touch event
* @param e
*/
onTouchClose(e: MouseEvent): Promise<void> {
return Object.throw();
}
}