@reusable-ui/orientationable
Version:
A capability of UI to rotate its layout.
261 lines (204 loc) • 12.3 kB
text/typescript
// cssfn:
import {
// cssfn css specific types:
CssRule,
CssStyleCollection,
CssSelectorCollection,
CssClassName,
// writes css in javascript:
rule,
startsCapitalized,
} from '@cssfn/core' // writes css in javascript
// hooks:
// variants:
//#region orientationable
//#region caches
const orientationClassesCache = new Map<OrientationName|OrientationWithDirectionName|undefined, CssClassName|null>();
export const createOrientationClass = (orientationName: OrientationName|OrientationWithDirectionName|undefined): CssClassName|null => {
const cached = orientationClassesCache.get(orientationName);
if (cached !== undefined) return cached; // null is allowed
if (orientationName === undefined) {
orientationClassesCache.set(orientationName, null);
return null;
} // if
const orientationClass = `o${startsCapitalized(orientationName)}`;
orientationClassesCache.set(orientationName, orientationClass);
return orientationClass;
};
let defaultInlineOrientationableStuffsCache : WeakRef<OrientationableStuff>|undefined = undefined;
let defaultBlockOrientationableStuffsCache : WeakRef<OrientationableStuff>|undefined = undefined;
// rarely used:
// let defaultInlineStartOrientationableWithDirectionStuffsCache : WeakRef<OrientationableWithDirectionStuff>|undefined = undefined;
// let defaultInlineEndOrientationableWithDirectionStuffsCache : WeakRef<OrientationableWithDirectionStuff>|undefined = undefined;
// let defaultBlockStartOrientationableWithDirectionStuffsCache : WeakRef<OrientationableWithDirectionStuff>|undefined = undefined;
// let defaultBlockEndOrientationableWithDirectionStuffsCache : WeakRef<OrientationableWithDirectionStuff>|undefined = undefined;
//#endregion caches
// utilities:
const fallbacks = <TValue>(value1: TValue|undefined, value2: TValue|undefined, value3: TValue): TValue => {
if (value1 !== undefined) return value1;
if (value2 !== undefined) return value2;
return value3;
};
export type OrientationName = 'inline'|'block'
export interface OrientationableStuff {
defaultOrientation : OrientationName
orientationInlineSelector : CssSelectorCollection
orientationBlockSelector : CssSelectorCollection
ifOrientationInline : (styles: CssStyleCollection) => CssRule
ifOrientationBlock : (styles: CssStyleCollection) => CssRule
}
export type OrientationableOptions = Omit<Partial<OrientationableStuff>, 'ifOrientationInline'|'ifOrientationBlock'>
export const defaultInlineOrientationableOptions : OrientationableOptions = { defaultOrientation: 'inline' };
export const defaultBlockOrientationableOptions : OrientationableOptions = { defaultOrientation: 'block' };
export const usesOrientationable = (options?: OrientationableOptions, defaultOptions = defaultBlockOrientationableOptions): OrientationableStuff => {
const isDefaultBlock = (
(defaultOptions === defaultBlockOrientationableOptions)
&&
(
(options === undefined)
||
(options === defaultOptions)
||
(
(options.defaultOrientation === defaultOptions.defaultOrientation )
&&
(options.orientationInlineSelector === defaultOptions.orientationInlineSelector)
&&
(options.orientationBlockSelector === defaultOptions.orientationBlockSelector )
)
)
);
const isDefaultInline = !isDefaultBlock && (
(defaultOptions === defaultInlineOrientationableOptions)
&&
(
(options === undefined)
||
(options === defaultOptions)
||
(
(options.defaultOrientation === defaultOptions.defaultOrientation )
&&
(options.orientationInlineSelector === defaultOptions.orientationInlineSelector)
&&
(options.orientationBlockSelector === defaultOptions.orientationBlockSelector )
)
)
);
if (isDefaultBlock) {
const cached = defaultBlockOrientationableStuffsCache?.deref();
if (cached) return cached;
}
else if (isDefaultInline) {
const cached = defaultInlineOrientationableStuffsCache?.deref();
if (cached) return cached;
} // if
const defaultOrientation = fallbacks<OrientationName >(options?.defaultOrientation , defaultOptions.defaultOrientation , 'block');
const orientationInlineSelector = fallbacks<CssSelectorCollection>(options?.orientationInlineSelector , defaultOptions.orientationInlineSelector , ((defaultOrientation === 'inline') ? ':not(:is(.oBlock, .oBlock-start, .oBlock-end))' : ':is(.oInline, .oInline-start, .oInline-end)'));
const orientationBlockSelector = fallbacks<CssSelectorCollection>(options?.orientationBlockSelector , defaultOptions.orientationBlockSelector , ((defaultOrientation === 'block' ) ? ':not(:is(.oInline, .oInline-start, .oInline-end))' : ':is(.oBlock, .oBlock-start, .oBlock-end)' ));
const result : OrientationableStuff = {
...options, // preserves foreign props
defaultOrientation,
orientationInlineSelector,
orientationBlockSelector,
ifOrientationInline : (styles: CssStyleCollection) => rule(orientationInlineSelector, styles),
ifOrientationBlock : (styles: CssStyleCollection) => rule(orientationBlockSelector , styles),
};
if (isDefaultBlock) {
defaultBlockOrientationableStuffsCache = new WeakRef<OrientationableStuff>(result);
}
else if (isDefaultInline) {
defaultInlineOrientationableStuffsCache = new WeakRef<OrientationableStuff>(result);
} // if
return result;
};
export interface OrientationableProps {
// variants:
orientation ?: OrientationName
}
export const useOrientationable = ({orientation}: OrientationableProps, defaultOptions = defaultBlockOrientationableOptions) => ({
class: createOrientationClass(orientation) ?? null,
get isOrientationBlock(): boolean {
return(
(
orientation
??
defaultOptions.defaultOrientation
)?.startsWith('block')
??
false
);
},
get isOrientationVertical(): boolean {
/*
Assumes block === vertical, in most cases the `writing-mode` is `horizontal-tb`.
To check for the styling on element is quite expensive (needs to get the ref of the element and then may re-render).
For performance reason, we assume the `writing-mode = horizontal-tb`.
*/
return this.isOrientationBlock;
},
get 'aria-orientation'() {
return (
this.isOrientationVertical
?
'vertical'
:
'horizontal'
);
}
});
export type OrientationWithDirectionName = 'inline-start'|'inline-end'|'block-start'|'block-end'
export interface OrientationableWithDirectionStuff {
defaultOrientation : OrientationWithDirectionName
orientationInlineStartSelector : CssSelectorCollection
orientationInlineEndSelector : CssSelectorCollection
orientationBlockStartSelector : CssSelectorCollection
orientationBlockEndSelector : CssSelectorCollection
ifOrientationInlineStart : (styles: CssStyleCollection) => CssRule
ifOrientationInlineEnd : (styles: CssStyleCollection) => CssRule
ifOrientationBlockStart : (styles: CssStyleCollection) => CssRule
ifOrientationBlockEnd : (styles: CssStyleCollection) => CssRule
}
export type OrientationableWithDirectionOptions = Omit<Partial<OrientationableWithDirectionStuff>, 'ifOrientationInlineStart'|'ifOrientationInlineEnd'|'ifOrientationBlockStart'|'ifOrientationBlockEnd'>
export const defaultInlineStartOrientationableWithDirectionOptions : OrientationableWithDirectionOptions = { defaultOrientation: 'inline-start' };
export const defaultInlineEndOrientationableWithDirectionOptions : OrientationableWithDirectionOptions = { defaultOrientation: 'inline-end' };
export const defaultBlockStartOrientationableWithDirectionOptions : OrientationableWithDirectionOptions = { defaultOrientation: 'block-start' };
export const defaultBlockEndOrientationableWithDirectionOptions : OrientationableWithDirectionOptions = { defaultOrientation: 'block-end' };
export const usesOrientationableWithDirection = (options?: OrientationableWithDirectionOptions, defaultOptions = defaultBlockEndOrientationableWithDirectionOptions): OrientationableWithDirectionStuff & Pick<OrientationableStuff, 'ifOrientationInline'|'ifOrientationBlock'> => {
const defaultOrientation = fallbacks<OrientationWithDirectionName>(options?.defaultOrientation , defaultOptions.defaultOrientation , 'block-end');
const orientationInlineStartSelector = fallbacks<CssSelectorCollection >(options?.orientationInlineStartSelector , defaultOptions.orientationInlineStartSelector , ((defaultOrientation === 'inline-start') ? ':not(:is(.oBlock-start, .oBlock-end, .oInline-end))' : '.oInline-start'));
const orientationInlineEndSelector = fallbacks<CssSelectorCollection >(options?.orientationInlineEndSelector , defaultOptions.orientationInlineEndSelector , ((defaultOrientation === 'inline-end' ) ? ':not(:is(.oBlock-start, .oBlock-end, .oInline-start))' : '.oInline-end' ));
const orientationBlockStartSelector = fallbacks<CssSelectorCollection >(options?.orientationBlockStartSelector , defaultOptions.orientationBlockStartSelector , ((defaultOrientation === 'block-start' ) ? ':not(:is(.oInline-start, .oInline-end, .oBlock-end))' : '.oBlock-start' ));
const orientationBlockEndSelector = fallbacks<CssSelectorCollection >(options?.orientationBlockEndSelector , defaultOptions.orientationBlockEndSelector , ((defaultOrientation === 'block-end' ) ? ':not(:is(.oInline-start, .oInline-end, .oBlock-start))' : '.oBlock-end' ));
return {
...options, // preserves foreign props
defaultOrientation,
orientationInlineStartSelector,
orientationInlineEndSelector,
orientationBlockStartSelector,
orientationBlockEndSelector,
ifOrientationInlineStart : (styles: CssStyleCollection) => rule(orientationInlineStartSelector, styles),
ifOrientationInlineEnd : (styles: CssStyleCollection) => rule(orientationInlineEndSelector , styles),
ifOrientationBlockStart : (styles: CssStyleCollection) => rule(orientationBlockStartSelector , styles),
ifOrientationBlockEnd : (styles: CssStyleCollection) => rule(orientationBlockEndSelector , styles),
ifOrientationInline : (styles: CssStyleCollection) => rule([orientationInlineStartSelector, orientationInlineEndSelector], styles),
ifOrientationBlock : (styles: CssStyleCollection) => rule([orientationBlockStartSelector , orientationBlockEndSelector ], styles),
};
};
export interface OrientationableWithDirectionProps {
// variants:
orientation ?: OrientationWithDirectionName
}
export const useOrientationableWithDirection = ({orientation}: OrientationableWithDirectionProps, defaultOptions = defaultBlockEndOrientationableWithDirectionOptions) => ({
class: createOrientationClass(orientation) ?? null,
get orientation(): OrientationWithDirectionName {
return(
orientation
??
defaultOptions.defaultOrientation
??
defaultBlockEndOrientationableWithDirectionOptions.defaultOrientation!
);
},
});
//#endregion orientationable