UNPKG

@reusable-ui/disableable

Version:

A capability of UI to be disabled.

164 lines (163 loc) 6.34 kB
// cssfn: import { // writes css in javascript: rule, states, style, vars, cssVars, } from '@cssfn/core'; // writes css in javascript // reusable-ui utilities: import { useSemantic, } from '@reusable-ui/semantics'; // a semantic management system for react web components import { // hooks: usePropEnabled, } from '@reusable-ui/accessibilities'; // an accessibility management system import { // hooks: useAnimatingState, } from '@reusable-ui/animating-state'; // a hook for creating animating state // reusable-ui features: import { // hooks: usesAnimation, } from '@reusable-ui/animation'; // animation stuff of UI const [disableableVars] = cssVars({ prefix: 'di', minify: false }); // shared variables: ensures the server-side & client-side have the same generated css variable names { const { animationRegistry: { registerFilter, registerAnim } } = usesAnimation(); registerFilter(disableableVars.filter); registerAnim(disableableVars.anim); } // if all below are not set => enabled: const selectorIfEnabled = ':not(:is(.enabling, .disabling, [aria-disabled]:not([aria-disabled="false"]), :disabled, .disabled))'; // .enabling will be added after loosing disable and will be removed after enabling-animation done: const selectorIfEnabling = '.enabling'; // .disabling, [aria-disabled] = styled disable, :disabled = native disable: const selectorIfDisabling = ':is(.disabling, [aria-disabled]:not([aria-disabled="false"]), :disabled):not(:is(.enabling, .disabled))'; // .disabled will be added after disabling-animation done: const selectorIfDisabled = '.disabled'; export const ifEnabled = (styles) => rule(selectorIfEnabled, styles); export const ifEnabling = (styles) => rule(selectorIfEnabling, styles); export const ifDisabling = (styles) => rule(selectorIfDisabling, styles); export const ifDisabled = (styles) => rule(selectorIfDisabled, styles); export const ifEnable = (styles) => rule([selectorIfEnabling, selectorIfEnabled], styles); export const ifDisable = (styles) => rule([selectorIfDisabling, selectorIfDisabled], styles); export const ifEnablingDisable = (styles) => rule([selectorIfEnabling, selectorIfDisabling, selectorIfDisabled], styles); /** * Adds a capability of UI to be disabled. * @param config A configuration of `disableableRule`. * @returns A `DisableableStuff` represents a disableable state. */ export const usesDisableable = (config) => { return { disableableRule: () => style({ // animation states: ...states([ ifEnabling({ ...vars({ [disableableVars.filter]: config?.filterDisable, [disableableVars.anim]: config?.animEnable, }), }), ifDisabling({ ...vars({ [disableableVars.filter]: config?.filterDisable, [disableableVars.anim]: config?.animDisable, }), }), ifDisabled({ ...vars({ [disableableVars.filter]: config?.filterDisable, }), }), ]), }), disableableVars, }; }; const htmlCtrls = [ 'button', 'fieldset', 'input', 'select', 'optgroup', 'option', 'textarea', ]; export const useDisableable = (props) => { // fn props: const propEnabled = usePropEnabled(props); const { tag } = useSemantic(props); // fn states: /* * state is enabled/disabled based on [controllable enabled] * [uncontrollable enabled] is not supported */ const enabledFn = propEnabled /*controllable*/; // states: const [enabled, setEnabled, animation, { handleAnimationStart, handleAnimationEnd, handleAnimationCancel }] = useAnimatingState({ initialState: enabledFn, animationName: /((^|[^a-z])(enable|disable)|([a-z])(Enable|Disable))(?![a-z])/, }); // update state: if (enabled !== enabledFn) { // change detected => apply the change & start animating setEnabled(enabledFn); // remember the last change } // if // fn props: const state = (() => { // enabling: if (animation === true) return 2 /* DisableableState.Enabling */; // disabling: if (animation === false) return 1 /* DisableableState.Disabling */; // fully disabled: if (!enabled) return 0 /* DisableableState.Disabled */; // fully enabled: return 3 /* DisableableState.Enabled */; })(); const stateClass = (() => { switch (state) { // enabling: case 2 /* DisableableState.Enabling */: { return 'enabling'; } ; // disabling: case 1 /* DisableableState.Disabling */: { // [enabled] but *still* animating of disabling => force to keep disabling using class .disabling if (enabled) return 'disabling'; return null; // uses :disabled or [aria-disabled] } ; // fully disabled: case 0 /* DisableableState.Disabled */: { return 'disabled'; } ; // fully enabled: case 3 /* DisableableState.Enabled */: { return null; } ; } // switch })(); // api: return { enabled: enabled, disabled: !enabled, state: state, class: stateClass, props: (() => { if (enabled) return null; // use :disabled if <control>: if (tag && htmlCtrls.includes(tag)) return { disabled: true }; // else, use [aria-disabled]: return { 'aria-disabled': true }; })(), handleAnimationStart, handleAnimationEnd, handleAnimationCancel, }; }; //#endregion disableable