@v4fire/client
Version:
V4Fire client core library
222 lines (181 loc) • 4.92 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-access/README.md]]
* @packageDocumentation
*/
import SyncPromise from 'core/promise/sync';
import type iBlock from 'super/i-block/i-block';
import type { ModsDecl, ModEvent } from 'super/i-block/i-block';
export default abstract class iAccess {
/**
* Trait modifiers
*/
static readonly mods: ModsDecl = {
disabled: [
'true',
'false'
],
focused: [
'true',
'false'
]
};
/** @see [[iAccess.disable]] */
static disable: AddSelf<iAccess['disable'], iBlock> =
(component) => SyncPromise.resolve(component.setMod('disabled', true));
/** @see [[iAccess.enable]] */
static enable: AddSelf<iAccess['enable'], iBlock> =
(component) => SyncPromise.resolve(component.setMod('disabled', false));
/** @see [[iAccess.focus]] */
static focus: AddSelf<iAccess['focus'], iBlock> =
(component) => SyncPromise.resolve(component.setMod('focused', true));
/** @see [[iAccess.blur]] */
static blur: AddSelf<iAccess['blur'], iBlock> =
(component) => SyncPromise.resolve(component.setMod('focused', false));
/**
* Returns true if the component in focus
* @param component
*/
static isFocused<T extends iBlock>(component: T): boolean {
return component.mods.focused === 'true';
}
/**
* Initializes modifier event listeners for the specified component
*
* @emits `enable()`
* @emits `disable()`
*
* @emits `focus()`
* @emits `blur()`
*
* @param component
*/
static initModEvents<T extends iBlock>(component: T): void {
const {
async: $a,
localEmitter: $e
} = component.unsafe;
$e.on('block.mod.*.disabled.*', (e: ModEvent) => {
const asyncGroup = {
group: 'disableHelpers'
};
$a.off(asyncGroup);
const
enabled = e.value === 'false' || e.type === 'remove';
if (enabled) {
if (e.type !== 'remove' || e.reason === 'removeMod') {
component.emit('enable');
}
} else {
component.emit('disable');
}
return component.waitStatus('ready', setAttrs, asyncGroup);
function setAttrs(): void {
const
{$el} = component;
if ($el == null) {
return;
}
$el.setAttribute('aria-disabled', String(!enabled));
if (!enabled) {
const handler = (e) => {
e.preventDefault();
e.stopImmediatePropagation();
};
// @see https://github.com/V4Fire/Client/issues/534
$a.on($el, 'click mousedown touchstart keydown input change scroll', handler, {
...asyncGroup,
options: {
capture: true
}
});
}
}
});
$e.on('block.mod.*.focused.*', (e: ModEvent) => {
const asyncGroup = {
group: 'focusHelpers'
};
$a.off(asyncGroup);
const
focused = e.value !== 'false' && e.type !== 'remove';
if (e.type !== 'remove' || e.reason === 'removeMod') {
component.emit(focused ? 'focus' : 'blur');
}
return component.waitStatus('ready', setAttrs, asyncGroup);
function setAttrs(): void {
const
{$el} = component;
if ($el == null) {
return;
}
if ($el.hasAttribute('tab-index')) {
const
el = (<HTMLButtonElement>$el);
if (focused) {
el.focus();
} else {
el.blur();
}
}
}
});
}
/**
* A Boolean attribute which, if present, indicates that the component should automatically
* have focus when the page has finished loading (or when the `<dialog>` containing the element has been displayed)
*
* @prop
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautofocus
*/
abstract autofocus?: boolean;
/**
* An integer attribute indicating if the component can take input focus (is focusable),
* if it should participate to sequential keyboard navigation.
* As all input types except for input of type hidden are focusable, this attribute should not be used on
* form controls, because doing so would require the management of the focus order for all elements within
* the document with the risk of harming usability and accessibility if done incorrectly.
*
* @prop
*/
abstract tabIndex?: number;
/**
* True if the component in focus
*/
abstract isFocused: boolean;
/**
* Enables the component
* @param args
*/
enable(...args: unknown[]): Promise<boolean> {
return Object.throw();
}
/**
* Disables the component
* @param args
*/
disable(...args: unknown[]): Promise<boolean> {
return Object.throw();
}
/**
* Sets the focus to the component
* @param args
*/
focus(...args: unknown[]): Promise<boolean> {
return Object.throw();
}
/**
* Unsets the focus from the component
* @param args
*/
blur(...args: unknown[]): Promise<boolean> {
return Object.throw();
}
}