From 24c0a2b7669871859b86eb61853848a56cb2fd08 Mon Sep 17 00:00:00 2001 From: Marco 'Lubber' Wienkoop Date: Thu, 31 Oct 2024 20:33:15 +0100 Subject: [PATCH] feat(slider): improve slider variants and gapratio This PR enhances the slider component, so it - fixes some slider issues - tick color on inverted variant - tick position on vertical variant - wrong gapratio calculation when calculated number of labels is a prime number - adds internal caching of otherwise often repeated calculations - improves labelType "letter" option, - which now supports a custom letter string or array (was hardcoded before) - "increases" the letters in case more labels than given letters are needed (like the columns of a spreadsheet) - adds another variant vertical right aligned - adds an optional value fixed for autoAdjustLabels - adds another option highlightRange to slightly highlight the selected area range - supports the usage of ui label --- src/definitions/modules/slider.js | 180 ++++++++++++++---- src/definitions/modules/slider.less | 111 ++++++++++- .../default/globals/variation.variables | 4 + src/themes/default/modules/slider.variables | 10 + 4 files changed, 255 insertions(+), 50 deletions(-) diff --git a/src/definitions/modules/slider.js b/src/definitions/modules/slider.js index fda915221e..7a77a7ce2b 100644 --- a/src/definitions/modules/slider.js +++ b/src/definitions/modules/slider.js @@ -32,8 +32,6 @@ methodInvoked = typeof query === 'string', queryArguments = [].slice.call(arguments, 1), - alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], - SINGLE_STEP = 1, BIG_STEP = 2, NO_STEP = 0, @@ -82,7 +80,6 @@ position, secondPos, offset, - precision, gapRatio = 1, previousValue, @@ -125,6 +122,7 @@ clearInterval(instance.interval); module.unbind.events(); module.unbind.slidingEvents(); + delete module.cache; $module.removeData(moduleNamespace); instance = undefined; }, @@ -141,7 +139,7 @@ + '
' + ''); } - precision = module.get.precision(); + module.clear.cache(); $thumb = $module.find('.thumb:not(.second)'); if (settings.showThumbTooltip) { $thumb @@ -175,8 +173,14 @@ module.setup.autoLabel(); } + if (settings.highlightRange) { + $labels.addClass(className.active); + } + if (settings.showLabelTicks) { $module.addClass(className.ticked); + } else if ($module.hasClass(className.ticked)) { + settings.showLabelTicks = 'always'; } } }, @@ -211,14 +215,20 @@ } else { $labels = $module.append('').find('.labels'); } - for (var i = 0, len = module.get.numLabels(); i <= len; i++) { + var step = module.get.step(), + precision = module.get.precision(), + len = module.get.numLabels(), + ignoreLabels = len - (settings.autoAdjustLabels !== 'fixed' ? 0 : module.get.max().toString().length + 4) + ; + for (var i = 0; i <= len; i++) { var - labelText = module.get.label(i), + stepValue = Math.round(((i * (step === 0 ? 1 : step)) + module.get.min()) * precision) / precision, + labelText = module.get.label(i, stepValue), showLabel = settings.restrictedLabels.length === 0 || settings.restrictedLabels.indexOf(labelText) >= 0, $label = labelText !== '' && (showLabel || settings.showLabelTicks === 'always') - ? (!(i % module.get.gapRatio()) - ? $('
  • ' + (showLabel ? labelText : '') + '
  • ') - : $('
  • ')) + ? ((!(i % module.get.gapRatio()) && i < ignoreLabels) || i === len + ? $('
  • ', { class: className.label, 'data-value': stepValue, html: showLabel ? labelText : '' }) + : $('
  • ', { class: 'halftick label', 'data-value': stepValue })) : null, ratio = i / len ; @@ -489,6 +499,12 @@ }, }, + clear: { + cache: function () { + module.cache = {}; + }, + }, + resync: function () { module.verbose('Resyncing thumb position based on value'); if (module.is.range()) { @@ -538,6 +554,25 @@ }, is: { + prime: function (n) { + if (module.cache['prime' + n] === undefined) { + var p = true; + for (var i = 2, s = Math.sqrt(n); i <= s; i++) { + if (n % i === 0) { + p = false; + + break; + } + } + if (p) { + p = n > 1; + } + + module.cache['prime' + n] = p; + } + + return module.cache['prime' + n]; + }, range: function () { var isRange = $module.hasClass(className.range); if (!isRange && (settings.minRange || settings.maxRange)) { @@ -652,62 +687,87 @@ return margin || '0px'; }, precision: function () { - var - decimalPlaces, - step = module.get.step() - ; - if (step !== 0) { - var split = String(step).split('.'); - decimalPlaces = split.length === 2 ? split[1].length : 0; - } else { - decimalPlaces = settings.decimalPlaces; + if (module.cache.precision === undefined) { + var + decimalPlaces, + step = module.get.step() + ; + if (step !== 0) { + var split = String(step).split('.'); + decimalPlaces = split.length === 2 ? split[1].length : 0; + } else { + decimalPlaces = settings.decimalPlaces; + } + var precision = Math.pow(10, decimalPlaces); + module.debug('Precision determined', precision); + module.cache.precision = precision; } - var precision = Math.pow(10, decimalPlaces); - module.debug('Precision determined', precision); - return precision; + return module.cache.precision; }, min: function () { return settings.min; }, max: function () { - var - step = module.get.step(), - min = module.get.min(), - precision = module.get.precision(), - quotient = step === 0 ? 0 : Math.floor(Math.round(((settings.max - min) / step) * precision) / precision), - remainder = step === 0 ? 0 : (settings.max - min) % step - ; + if (module.cache.max === undefined) { + var + step = module.get.step(), + min = module.get.min(), + precision = module.get.precision(), + quotient = step === 0 ? 0 : Math.floor(Math.round(((settings.max - min) / step) * precision) / precision), + remainder = step === 0 ? 0 : (settings.max - min) % step + ; + if (remainder > 0) { + module.debug('Max value not divisible by given step. Increasing max value.', settings.max, step); + } + module.cache.max = remainder === 0 ? settings.max : min + quotient * step; + } - return remainder === 0 ? settings.max : min + quotient * step; + return module.cache.max; }, step: function () { return settings.step; }, numLabels: function () { - var step = module.get.step(), - precision = module.get.precision(), - value = Math.round(((module.get.max() - module.get.min()) / (step === 0 ? 1 : step)) * precision) / precision; - module.debug('Determined that there should be ' + value + ' labels'); + if (module.cache.numLabels === undefined) { + var step = module.get.step(), + precision = module.get.precision(), + value = Math.round(((module.get.max() - module.get.min()) / (step === 0 ? 1 : step)) * precision) / precision; + module.debug('Determined that there should be ' + value + ' labels'); + module.cache.numLabels = value; + } - return value; + return module.cache.numLabels; }, labelType: function () { return settings.labelType; }, - label: function (value) { - if (interpretLabel) { - return interpretLabel(value); + label: function (value, stepValue) { + if (isFunction(interpretLabel)) { + return interpretLabel(value, stepValue, module); } switch (settings.labelType) { case settings.labelTypes.number: { - var step = module.get.step(); - - return Math.round(((value * (step === 0 ? 1 : step)) + module.get.min()) * precision) / precision; + return stepValue; } case settings.labelTypes.letter: { - return alphabet[value % 26]; + if (value < 0 || module.get.precision() > 1) { + module.error(error.invalidLetterNumber, value); + + return value; + } + var letterLabel = '', + letters = Array.isArray(settings.letters) ? settings.letters : String(settings.letters).split(''), + lettersLen = letters.length + ; + + while (stepValue >= 0) { + letterLabel = letters[stepValue % lettersLen] + letterLabel; + stepValue = Math.floor(stepValue / lettersLen) - 1; + } + + return letterLabel; } default: { return value; @@ -717,6 +777,9 @@ value: function () { return value; }, + settings: function () { + return settings; + }, currentThumbValue: function () { return $currThumb !== undefined && $currThumb.hasClass('second') ? module.secondThumbVal : module.thumbVal; }, @@ -761,6 +824,7 @@ if (settings.autoAdjustLabels) { var numLabels = module.get.numLabels(), + primePlus = module.is.prime(numLabels) ? 1 : 0, trackLength = module.get.trackLength(), gapCounter = 1 ; @@ -770,7 +834,7 @@ // and apply only if the modulo of the operation is an odd number. if (trackLength > 0) { while ((trackLength / numLabels) * gapCounter < settings.labelDistance) { - if (!(numLabels % gapCounter)) { + if (!((numLabels + primePlus) % gapCounter) || settings.autoAdjustLabels === 'fixed') { gapRatio = gapCounter; } gapCounter += 1; @@ -908,6 +972,7 @@ }, value: function (position) { var + precision = module.get.precision(), startPos = module.is.reversed() ? module.get.trackEndPos() : module.get.trackStartPos(), endPos = module.is.reversed() ? module.get.trackStartPos() : module.get.trackEndPos(), ratio = (position - startPos) / (endPos - startPos), @@ -976,6 +1041,30 @@ }, set: { + active: function (thumbVal, secondThumbVal) { + if (settings.highlightRange) { + if (secondThumbVal < thumbVal) { + var tempVal = secondThumbVal; + secondThumbVal = thumbVal; + thumbVal = tempVal; + } + var $children = $labels.find('.label'); + $children.each(function (index) { + var + $child = $(this), + attrValue = $child.attr('data-value') + ; + if (attrValue) { + attrValue = parseInt(attrValue, 10); + if (attrValue >= thumbVal && attrValue <= secondThumbVal) { + $child.addClass(className.active); + } else { + $child.removeClass(className.active); + } + } + }); + } + }, value: function (newValue, fireChange) { fireChange = fireChange !== false; var toReset = previousValue === undefined; @@ -1103,6 +1192,7 @@ position = newPos; thumbVal = newValue; } + module.set.active(thumbVal, secondThumbVal); var trackPosValue, thumbPosValue, @@ -1210,6 +1300,7 @@ } else { return settings[name]; } + module.clear.cache(); }, internal: function (name, value) { if ($.isPlainObject(name)) { @@ -1382,6 +1473,7 @@ method: 'The method you called is not defined.', notrange: 'This slider is not a range slider', invalidRanges: 'Invalid range settings (start/end/minRange/maxRange)', + invalidLetterNumber: 'Negative values or decimal places for labelType: "letter" are not supported', }, metadata: { @@ -1404,6 +1496,7 @@ preventCrossover: true, fireOnInit: false, interpretLabel: false, + letters: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', // the decimal place to round to if step is undefined decimalPlaces: 2, @@ -1421,6 +1514,8 @@ vertical: 'vertical', range: 'range', smooth: 'smooth', + label: 'label', + active: 'active', }, keys: { @@ -1433,6 +1528,7 @@ }, restrictedLabels: [], + highlightRange: false, showThumbTooltip: false, tooltipConfig: { position: 'top center', diff --git a/src/definitions/modules/slider.less b/src/definitions/modules/slider.less index 59e0cfdaeb..6697d28135 100644 --- a/src/definitions/modules/slider.less +++ b/src/definitions/modules/slider.less @@ -204,15 +204,18 @@ .ui.labeled.slider > .labels .label { display: inline-flex; - padding: @labelPadding; position: absolute; transform: translate(-50%, -100%); white-space: nowrap; + &:not(.ui) { + padding: @labelPadding; + } } - - .ui.bottom.aligned.labeled.slider > .labels .label { - bottom: 0; - transform: translate(-50%, 100%); + & when (@variationSliderBottomAligned) { + .ui.bottom.aligned.labeled.slider > .labels .label { + bottom: 0; + transform: translate(-50%, 100%); + } } & when (@variationSliderTicked) { .ui.labeled.ticked.slider > .labels .label::after { @@ -224,13 +227,39 @@ top: 100%; left: 50%; } - .ui.bottom.aligned.labeled.ticked.slider > .labels .label::after { - top: auto; - bottom: 100%; + & when (@variationSliderBottomAligned) { + .ui.bottom.aligned.labeled.ticked.slider > .labels .label::after { + top: auto; + bottom: 100%; + } + & when (@variationSliderUiLabel) { + .ui.labeled.slider.bottom.aligned .labels .ui.label { + margin-bottom: -@uiLabelMargin; + } + .ui.labeled.ticked.slider.bottom.aligned .labels .ui.label::after { + margin-bottom: @uiLabelMargin; + } + } } .ui.labeled.ticked.slider > .labels .halftick.label::after { height: (@labelHeight / 2); } + & when (@variationSliderInverted) { + .ui.inverted.labeled.ticked.slider > .labels .label::after { + background: @invertedLabelColor; + } + } + } + & when (@variationSliderUiLabel) { + .ui.labeled.slider:not(.vertical):not(.bottom) .labels .ui.label { + margin-top: -@uiLabelMargin; + } + .ui.labeled.ticked.slider:not(.vertical):not(.bottom) .labels .ui.label::after { + margin-top: @uiLabelMargin; + } + .ui.labeled.ticked.slider:not(.vertical) > .labels .ui.label::after { + height: @uiLabelTickHeight; + } } & when (@variationSliderVertical) { @@ -259,6 +288,9 @@ width: (@labelHeight / 2); height: @labelWidth; } + .ui.labeled.vertical.slider:not(.right) > .labels .halftick.label::after { + margin-left: @verticalTickDistance; + } & when (@variationSliderReversed) { /* Vertical Reversed Labels */ @@ -266,6 +298,39 @@ transform: translate(-100%, 50%); } } + & when (@variationSliderUiLabel) { + .ui.labeled.ticked.vertical.slider:not(.right) > .labels .ui.label::after { + margin-left: @verticalUiLabelMargin; + } + } + & when (@variationSliderRightAligned) { + .ui.labeled.vertical.right.aligned.slider > .labels { + transform: translateX(50%); + left: e(%("calc(100%% + %d)", @verticalTickDistance)); + } + .ui.labeled.vertical.right.aligned.slider > .labels .label { + transform: translate(100%, -50%); + right: 100%; + } + + .ui.labeled.vertical.right.aligned.slider > .labels .label::after { + right: 100%; + left: auto; + } + .ui.labeled.vertical.right.aligned.slider > .labels .halftick.label::after { + margin-right: @verticalTickDistance; + } + & when (@variationSliderReversed) { + .ui.labeled.vertical.reversed.right.aligned.slider > .labels .label { + transform: translate(100%, 50%); + } + } + & when (@variationSliderUiLabel) { + .ui.labeled.ticked.vertical.right.aligned.slider > .labels .ui.label::after { + margin-right: @verticalUiLabelMargin; + } + } + } } } @@ -296,6 +361,26 @@ background-color: @transparentWhite; } } +& when (@variationSliderHighlight) { + .ui.labeled.slider > .active.labels .label { + transition: @highlightTransition; + } + & when (@variationSliderTicked) { + .ui.labeled.ticked.slider > .active.labels .active.label::after { + background: @trackFillColor; + } + & when (@variationSliderInverted) { + .ui.inverted.labeled.ticked.slider > .active.labels .active.label::after { + background: @invertedTrackFillColor; + } + } + } + + .ui.labeled.ticked.slider > .active.labels .active.label::after, + .ui.labeled.slider > .active.labels .label:not(.active) { + opacity: @highlightOpacity; + } +} /* -------------- Colors @@ -338,6 +423,16 @@ } } } + & when (@variationSliderHighlight) and (@variationSliderTicked) { + .ui.@{color}.labeled.ticked.slider > .active.labels .active.label::after { + background-color: @c; + } + & when (@variationSliderInverted) { + .ui.@{color}.inverted.labeled.ticked.slider > .active.labels .active.label::after { + background-color: @l; + } + } + } }); } diff --git a/src/themes/default/globals/variation.variables b/src/themes/default/globals/variation.variables index 6c2da02ddb..bbb5758c5a 100644 --- a/src/themes/default/globals/variation.variables +++ b/src/themes/default/globals/variation.variables @@ -713,6 +713,10 @@ @variationSliderTicked: true; @variationSliderVertical: true; @variationSliderBasic: true; +@variationSliderHighlight: true; +@variationSliderBottomAligned: true; +@variationSliderRightAligned: true; +@variationSliderUiLabel: true; @variationSliderSizes: small, large, big; @variationSliderColors: @variationAllColors; diff --git a/src/themes/default/modules/slider.variables b/src/themes/default/modules/slider.variables index 67a2b7bb21..523863ee4f 100644 --- a/src/themes/default/modules/slider.variables +++ b/src/themes/default/modules/slider.variables @@ -14,6 +14,7 @@ @trackHeight: 0.4em; @trackPositionTop: (@height / 2) - (@trackHeight / 2); @background: #ccc; +@invertedBackground: #333; @trackBorderRadius: 4px; @trackColor: @transparentBlack; @@ -52,14 +53,23 @@ Variations -------------------- */ +/* Highlight */ +@highlightOpacity: 0.6; +@highlightTransition: all 0.2s @defaultEasing; + /* Vertical */ @verticalPadding: 0.5em 1em; +@verticalTickDistance: 0.6em; +@verticalUiLabelMargin: 0.3em; /* Labeled */ @labelHeight: @height; @labelWidth: 1px; @labelColor: @background; @labelPadding: 0.2em 0; +@invertedLabelColor: @invertedBackground; +@uiLabelMargin: 0.5em; +@uiLabelTickHeight: 2em; /* Hover */ @hoverVarOpacity: 0;