@reusable-ui/range
Version:
A UI for the user defines a numeric value in the specified range.
275 lines (274 loc) • 15.3 kB
JavaScript
// cssfn:
import {
// writes css in javascript:
rule, children, style,
// reads/writes css variables configuration:
usesCssProps, usesPrefixedProps, usesSuffixedProps, overwriteProps,
// writes complex stylesheets in simpler way:
watchChanges, } from '@cssfn/core'; // writes css in javascript
// reusable-ui core:
import {
// reusable common layouts:
fillTextLineHeightLayout, fillTextLineWidthLayout,
// border (stroke) stuff of UI:
usesBorder,
// padding (inner spacing) stuff of UI:
usesPadding,
// groups a list of UIs into a single UI:
usesGroupable, usesOrientationable,
// size options of UI:
usesResizable, } from '@reusable-ui/core'; // a set of reusable-ui packages which are responsible for building any component
// reusable-ui components:
import {
// styles:
onEditableControlStylesChange, usesEditableControlLayout, usesEditableControlVariants, usesEditableControlStates, } from '@reusable-ui/editable-control'; // a base component
// internals:
import {
// defaults:
defaultOrientationableOptions, } from '../defaults.js';
import {
// features:
usesRange, } from '../features/range.js';
import {
// elements:
inputElm, trackElm, trackLowerElm, trackUpperElm, thumbElm, } from './elements.js';
import {
// configs:
ranges, cssRangeConfig, } from './config.js';
// styles:
export const onRangeStylesChange = watchChanges(onEditableControlStylesChange, cssRangeConfig.onChange);
export const usesRangeLayout = (options) => {
// options:
const orientationableStuff = usesOrientationable(options, defaultOrientationableOptions);
const { ifOrientationInline, ifOrientationBlock, orientationInlineSelector, orientationBlockSelector } = orientationableStuff;
const parentOrientationInlineSelector = `${orientationInlineSelector}&`;
const parentOrientationBlockSelector = `${orientationBlockSelector}&`;
const ifParentOrientationInline = (styles) => rule(parentOrientationInlineSelector, styles);
const ifParentOrientationBlock = (styles) => rule(parentOrientationBlockSelector, styles);
// dependencies:
// features:
const { borderRule: trackBorderRule, borderVars: trackBorderVars, } = usesBorder(usesPrefixedProps(ranges, 'track'));
const { borderRule: thumbBorderRule, borderVars: thumbBorderVars, } = usesBorder(usesPrefixedProps(ranges, 'thumb'));
const { paddingRule: trackPaddingRule, paddingVars: trackPaddingVars, } = usesPadding(usesPrefixedProps(ranges, 'track'));
const { paddingRule: thumbPaddingRule, paddingVars: thumbPaddingVars, } = usesPadding(usesPrefixedProps(ranges, 'thumb'));
const { rangeRule, rangeVars } = usesRange();
// capabilities:
const { groupableRule } = usesGroupable({
orientationInlineSelector: parentOrientationInlineSelector,
orientationBlockSelector: parentOrientationBlockSelector,
itemsSelector: [trackLowerElm, trackUpperElm], // only select <trackLower> & <trackUpper>, do not modify the <thumb>
});
const { separatorRule } = usesGroupable({
orientationInlineSelector: parentOrientationInlineSelector,
orientationBlockSelector: parentOrientationBlockSelector,
itemsSelector: [trackLowerElm, trackUpperElm], // only select <trackLower> & <trackUpper>, do not modify the <thumb>
});
return style({
// layouts:
...usesEditableControlLayout(),
...style({
// layouts:
...ifOrientationInline({
display: 'flex', // use block flexbox, so it takes the entire parent's width
flexDirection: 'row', // items are stacked horizontally
}),
...ifOrientationBlock({
display: 'inline-flex', // use inline flexbox, so it takes the width & height as needed
flexDirection: 'column', // items are stacked vertically
}),
justifyContent: 'start', // if range is not growable, the excess space (if any) placed at the end, and if no sufficient space available => the range's first part should be visible first
alignItems: 'center', // default center items vertically
flexWrap: 'nowrap', // prevents the range to wrap to the next row
// positions:
verticalAlign: 'baseline', // <Range>'s text should be aligned with sibling text, so the <Range> behave like <span> wrapper
// accessibilities:
touchAction: 'pinch-zoom', // prevents scrolling by touch, but allows to zoom_in/out
WebkitTapHighlightColor: 'transparent', // no tap_&_hold highlight
userSelect: 'none', // disable selecting text (double clicking and hold_and_mouse_move not causing selecting the <Range>)
// children:
...ifOrientationInline({
...children('::before', {
// layouts:
...fillTextLineHeightLayout(), // adjust the <Range>'s height, the width is block (fills the entire parent's width)
}),
}),
...ifOrientationBlock({
...children('::before', {
// layouts:
...fillTextLineWidthLayout(), // adjust the <Range>'s width, the height is based on <Range>'s config
}),
}),
...children(inputElm, {
// layouts:
display: 'none', // hide the input
}),
...children(trackElm, {
// capabilities:
...groupableRule(), // make a nicely rounded corners
// features:
...trackBorderRule(),
...trackPaddingRule(),
// layouts:
...style({
// layouts:
display: 'flex', // use block flexbox, so it takes the entire Range's width
flexDirection: 'inherit', // customizable orientation // inherit to parent flexbox
justifyContent: 'start', // if thumb is not growable, the excess space (if any) placed at the end, and if no sufficient space available => the thumb's first part should be visible first
alignItems: 'center', // center thumb vertically
flexWrap: 'nowrap', // no wrapping
// sizes:
boxSizing: 'border-box', // the final size is including borders & paddings
flex: [[1, 1, '100%']], // growable, shrinkable, initial 100% parent's width
// accessibilities:
pointerEvents: 'none', // just an overlay element (ghost), no mouse interaction, clicking on it will focus on the parent
// animations:
boxShadow: ['none', '!important'], // no shadow & no focus animation
// children:
...children([trackLowerElm, trackUpperElm], {
// borders:
...separatorRule(), // turns the current border as separator between <trackLower> & <trackUpper>
// layouts:
...style({
// layouts:
display: 'inline-block', // use inline-block, so it takes the width & height as we set
// backgrounds:
backg: rangeVars.trackBackg,
// borders:
// a fix for track(Lower|Upper) background corners:
border: trackBorderVars.border,
// borderRadius : trackBorderVars.borderRadius,
borderStartStartRadius: trackBorderVars.borderStartStartRadius,
borderStartEndRadius: trackBorderVars.borderStartEndRadius,
borderEndStartRadius: trackBorderVars.borderEndStartRadius,
borderEndEndRadius: trackBorderVars.borderEndEndRadius,
[trackBorderVars.borderWidth]: '0px', // only setup borderRadius, no borderStroke
// sizes:
alignSelf: 'stretch', // follows parent's height
}),
}),
...children(trackLowerElm, {
// sizes:
// the size grows in proportion to the given ratio, starting from the 1/2 size of <thumb>
// the size cannot shrink
...ifParentOrientationInline({
flex: [[rangeVars.valueRatio, 0, `calc(${ranges.thumbInlineSize} / 2)`]], // growable, shrinkable, initial from 0 width; using `valueRatio` for the grow/shrink ratio
}),
...ifParentOrientationBlock({
flex: [[rangeVars.valueRatio, 0, `calc(${ranges.thumbBlockSize} / 2)`]], // growable, shrinkable, initial from 0 width; using `valueRatio` for the grow/shrink ratio
}),
// borders:
// removes border at the center of <thumb> when <Range mild={true}>:
...ifParentOrientationInline({
borderInlineEndWidth: '0px',
}),
...ifParentOrientationBlock({
borderBlockStartWidth: '0px',
}),
// customize:
...usesCssProps(usesPrefixedProps(ranges, 'tracklower')), // apply config's cssProps starting with tracklower***
}),
...children(trackUpperElm, {
// sizes:
// the size grows in proportion to the given ratio, starting from the 1/2 size of <thumb>
// the size cannot shrink
...ifParentOrientationInline({
flex: [[`calc(1 - ${rangeVars.valueRatio})`, 0, `calc(${ranges.thumbInlineSize} / 2)`]], // growable, shrinkable, initial from 0 width; using `1 - valueRatio` for the grow/shrink ratio
}),
...ifParentOrientationBlock({
flex: [[`calc(1 - ${rangeVars.valueRatio})`, 0, `calc(${ranges.thumbBlockSize} / 2)`]], // growable, shrinkable, initial from 0 width; using `1 - valueRatio` for the grow/shrink ratio
}),
// borders:
// removes border at the center of <thumb> when <Range mild={true}>:
...ifParentOrientationInline({
borderInlineStartWidth: '0px',
}),
...ifParentOrientationBlock({
borderBlockEndWidth: '0px',
}),
// customize:
...usesCssProps(usesPrefixedProps(ranges, 'trackupper')), // apply config's cssProps starting with trackupper***
}),
...children(['&', thumbElm], {
// accessibilities:
cursor: 'inherit',
}),
...children(thumbElm, {
// features:
...thumbBorderRule(),
...thumbPaddingRule(),
// layouts:
...style({
// layouts:
display: 'inline-block', // use inline-block, so it takes the width & height as we set
// positions:
zIndex: 1, // the <thumb> should at the top of <trackLower> & <trackUpper>
// sizes:
boxSizing: 'border-box', // the final size is including borders & paddings
// customize:
...usesCssProps(usesPrefixedProps(ranges, 'thumb')), // apply config's cssProps starting with thumb***
// borders:
border: thumbBorderVars.border,
// borderRadius : thumbBorderVars.borderRadius,
borderStartStartRadius: thumbBorderVars.borderStartStartRadius,
borderStartEndRadius: thumbBorderVars.borderStartEndRadius,
borderEndStartRadius: thumbBorderVars.borderEndStartRadius,
borderEndEndRadius: thumbBorderVars.borderEndEndRadius,
// spacings:
// padding : thumbPaddingVars.padding,
paddingInline: thumbPaddingVars.paddingInline,
paddingBlock: thumbPaddingVars.paddingBlock,
// cancel out <thumb>'s size with negative margin,
// so the <trackLower> & <trackUpper> can meet on the middle of the <thumb>:
marginInline: `calc(0px - (${ranges.thumbInlineSize}) / 2)`,
marginBlock: `calc(0px - (${ranges.thumbBlockSize}) / 2)`,
}),
}),
// customize:
...usesCssProps(usesPrefixedProps(ranges, 'track')), // apply config's cssProps starting with track***
// borders:
border: trackBorderVars.border,
// borderRadius : trackBorderVars.borderRadius,
borderStartStartRadius: trackBorderVars.borderStartStartRadius,
borderStartEndRadius: trackBorderVars.borderStartEndRadius,
borderEndStartRadius: trackBorderVars.borderEndStartRadius,
borderEndEndRadius: trackBorderVars.borderEndEndRadius,
// spacings:
// padding : trackPaddingVars.padding,
paddingInline: trackPaddingVars.paddingInline,
paddingBlock: trackPaddingVars.paddingBlock,
}),
}),
// customize:
...usesCssProps(ranges), // apply config's cssProps
...ifOrientationInline({
// overwrites propName = propName{Inline}:
...overwriteProps(ranges, usesSuffixedProps(ranges, 'inline')),
}),
...ifOrientationBlock({
// overwrites propName = propName{Block}:
...overwriteProps(ranges, usesSuffixedProps(ranges, 'block')),
}),
}),
// features:
...rangeRule(), // must be placed at the last
});
};
export const usesRangeVariants = () => {
// dependencies:
// variants:
const { resizableRule } = usesResizable(ranges);
return style({
// variants:
...usesEditableControlVariants(),
...resizableRule(),
});
};
export const usesRangeStates = usesEditableControlStates;
export default () => style({
// layouts:
...usesRangeLayout(),
// variants:
...usesRangeVariants(),
// states:
...usesRangeStates(),
});