UNPKG

@reusable-ui/range

Version:

A UI for the user defines a numeric value in the specified range.

461 lines (320 loc) 19.3 kB
// cssfn: import { // cssfn css specific types: CssStyleCollection, // 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, // a capability of UI to rotate its layout: OrientationableOptions, 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?: OrientationableOptions) => { // options: const orientationableStuff = usesOrientationable(options, defaultOrientationableOptions); const {ifOrientationInline, ifOrientationBlock, orientationInlineSelector, orientationBlockSelector} = orientationableStuff; const parentOrientationInlineSelector = `${orientationInlineSelector}&`; const parentOrientationBlockSelector = `${orientationBlockSelector }&`; const ifParentOrientationInline = (styles: CssStyleCollection) => rule(parentOrientationInlineSelector, styles); const ifParentOrientationBlock = (styles: CssStyleCollection) => rule(parentOrientationBlockSelector , styles); // dependencies: // features: const { borderRule: trackBorderRule, borderVars: trackBorderVars, } = usesBorder( usesPrefixedProps(ranges, 'track'), // fetch config's cssProps starting with track*** ); const { borderRule: thumbBorderRule, borderVars: thumbBorderVars, } = usesBorder( usesPrefixedProps(ranges, 'thumb'), // fetch config's cssProps starting with thumb*** ); const { paddingRule: trackPaddingRule, paddingVars: trackPaddingVars, } = usesPadding( usesPrefixedProps(ranges, 'track'), // fetch config's cssProps starting with track*** ); const { paddingRule: thumbPaddingRule, paddingVars: thumbPaddingVars, } = usesPadding( usesPrefixedProps(ranges, 'thumb'), // fetch config's cssProps starting with 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({ // inline display : 'flex', // use block flexbox, so it takes the entire parent's width flexDirection : 'row', // items are stacked horizontally }), ...ifOrientationBlock({ // block 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({ // inline ...children('::before', { // layouts: ...fillTextLineHeightLayout(), // adjust the <Range>'s height, the width is block (fills the entire parent's width) }), }), ...ifOrientationBlock({ // block ...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({ // inline flex : [[rangeVars.valueRatio, 0, `calc(${ranges.thumbInlineSize} / 2)`]], // growable, shrinkable, initial from 0 width; using `valueRatio` for the grow/shrink ratio }), ...ifParentOrientationBlock({ // block 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({ // inline borderInlineEndWidth: '0px', }), ...ifParentOrientationBlock({ // block 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({ // inline 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({ // block 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({ // inline borderInlineStartWidth: '0px', }), ...ifParentOrientationBlock({ // block 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({ // inline // overwrites propName = propName{Inline}: ...overwriteProps(ranges, usesSuffixedProps(ranges, 'inline')), }), ...ifOrientationBlock({ // block // 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(), });