diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example14.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example14.ts
index e6d326310..528d20b48 100644
--- a/examples/webpack-demo-vanilla-bundle/src/examples/example14.ts
+++ b/examples/webpack-demo-vanilla-bundle/src/examples/example14.ts
@@ -12,6 +12,7 @@ import {
GridOption,
LongTextEditorOption,
SlickNamespace,
+ SliderRangeOption,
SortComparers,
// utilities
@@ -172,10 +173,17 @@ export class Example14 {
formatter: Formatters.dollar,
},
{
- id: 'percentComplete', name: '% Complete', field: 'percentComplete', minWidth: 100,
+ id: 'percentComplete', name: '% Complete', field: 'percentComplete', minWidth: 150,
type: FieldType.number,
sortable: true, filterable: true, columnGroup: 'Analysis',
- filter: { model: Filters.compoundSlider, operator: '>=' },
+ filter: {
+ model: Filters.sliderRange,
+ operator: '>=',
+ filterOptions: {
+ enableSliderTrackColoring: true,
+ hideSliderNumbers: false,
+ } as SliderRangeOption,
+ },
editor: {
model: Editors.slider,
minValue: 0, maxValue: 100,
diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts
index 599612327..b1be7bf53 100644
--- a/packages/common/src/constants.ts
+++ b/packages/common/src/constants.ts
@@ -78,6 +78,9 @@ export class Constants {
TREE_LEVEL_PROP: '__treeLevel',
PARENT_PROP: '__parentId',
};
+ static readonly SLIDER_DEFAULT_MIN_VALUE = 0;
+ static readonly SLIDER_DEFAULT_MAX_VALUE = 100;
+ static readonly SLIDER_DEFAULT_STEP = 1;
static readonly VALIDATION_REQUIRED_FIELD = 'Field is required';
static readonly VALIDATION_EDITOR_VALID_NUMBER = 'Please enter a valid number';
static readonly VALIDATION_EDITOR_VALID_INTEGER = 'Please enter a valid integer number';
diff --git a/packages/common/src/filters/__tests__/sliderRangeFilter.spec.ts b/packages/common/src/filters/__tests__/sliderRangeFilter.spec.ts
new file mode 100644
index 000000000..2a31d4c55
--- /dev/null
+++ b/packages/common/src/filters/__tests__/sliderRangeFilter.spec.ts
@@ -0,0 +1,343 @@
+import { Filters } from '../filters.index';
+import { Column, FilterArguments, GridOption, SlickGrid, SlickNamespace } from '../../interfaces/index';
+import { SliderRangeFilter } from '../sliderRangeFilter';
+
+const containerId = 'demo-container';
+declare const Slick: SlickNamespace;
+
+// define a
container to simulate the grid container
+const template = `
`;
+
+const gridOptionMock = {
+ enableFiltering: true,
+ enableFilterTrimWhiteSpace: true,
+} as GridOption;
+
+const gridStub = {
+ getOptions: () => gridOptionMock,
+ getColumns: jest.fn(),
+ getHeaderRowColumn: jest.fn(),
+ render: jest.fn(),
+ onHeaderMouseLeave: new Slick.Event(),
+} as unknown as SlickGrid;
+
+describe('SliderRangeFilter', () => {
+ let consoleSpy: any;
+ let divContainer: HTMLDivElement;
+ let filter: SliderRangeFilter;
+ let filterArguments: FilterArguments;
+ let spyGetHeaderRow;
+ let mockColumn: Column;
+
+ beforeEach(() => {
+ consoleSpy = jest.spyOn(global.console, 'warn').mockReturnValue();
+ divContainer = document.createElement('div');
+ divContainer.innerHTML = template;
+ document.body.appendChild(divContainer);
+ spyGetHeaderRow = jest.spyOn(gridStub, 'getHeaderRowColumn').mockReturnValue(divContainer);
+
+ mockColumn = { id: 'duration', field: 'duration', filterable: true, filter: { model: Filters.sliderRange } };
+ filterArguments = {
+ grid: gridStub,
+ columnDef: mockColumn,
+ callback: jest.fn(),
+ filterContainerElm: gridStub.getHeaderRowColumn(mockColumn.id)
+ };
+
+ filter = new SliderRangeFilter();
+ });
+
+ afterEach(() => {
+ filter.destroy();
+ });
+
+ it('should throw an error when trying to call init without any arguments', () => {
+ expect(() => filter.init(null as any)).toThrowError('[Slickgrid-Universal] A filter must always have an "init()" with valid arguments.');
+ });
+
+ it('should initialize the filter', () => {
+ filter.init(filterArguments);
+ const filterCount = divContainer.querySelectorAll('.search-filter.slider-container.filter-duration').length;
+
+ expect(spyGetHeaderRow).toHaveBeenCalled();
+ expect(filterCount).toBe(1);
+ });
+
+ it('should be able to retrieve default slider options through the Getter', () => {
+ filter.init(filterArguments);
+
+ expect(filter.sliderRangeOptions).toEqual({
+ maxValue: 100,
+ minValue: 0,
+ step: 1,
+ });
+ });
+
+ it('should be able to retrieve slider options defined through the Getter when passing different filterOptions', () => {
+ mockColumn.filter = {
+ minValue: 4,
+ maxValue: 69,
+ valueStep: 5,
+ };
+ filter.init(filterArguments);
+
+ expect(filter.sliderRangeOptions).toEqual({
+ maxValue: 69,
+ minValue: 4,
+ step: 5,
+ });
+ });
+
+ it('should call "setValues" and expect that value to be in the callback when triggered', () => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filter.init(filterArguments);
+ filter.setValues(['2..80']);
+ const filterElms = divContainer.querySelectorAll
('.search-filter.slider-container.filter-duration input');
+ filterElms[0].dispatchEvent(new CustomEvent('change'));
+
+ expect(spyCallback).toHaveBeenLastCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'RangeInclusive', searchTerms: [2, 80], shouldTriggerQuery: true });
+ });
+
+ it('should call "setValues" and expect that value to be in the callback when triggered', () => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filter.init(filterArguments);
+ filter.setValues([3, 84]);
+ const sliderInputs = divContainer.querySelectorAll('.slider-filter-input');
+ const filterElms = divContainer.querySelectorAll('.search-filter.slider-container.filter-duration input');
+ filterElms[0].dispatchEvent(new CustomEvent('change'));
+
+ expect(sliderInputs[0].style.zIndex).toBe('0');
+ expect(sliderInputs[1].style.zIndex).toBe('1');
+ expect(spyCallback).toHaveBeenLastCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'RangeInclusive', searchTerms: [3, 84], shouldTriggerQuery: true });
+ });
+
+ it('should change z-index on left handle when it is by 20px near right handle so it shows over the right handle not below', () => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filter.init(filterArguments);
+ filter.setValues([50, 63]);
+ const sliderInputs = divContainer.querySelectorAll('.slider-filter-input');
+ const filterElms = divContainer.querySelectorAll('.search-filter.slider-container.filter-duration input');
+ filterElms[0].dispatchEvent(new CustomEvent('change'));
+
+ expect(sliderInputs[0].style.zIndex).toBe('1');
+ expect(sliderInputs[1].style.zIndex).toBe('0');
+ expect(spyCallback).toHaveBeenLastCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'RangeInclusive', searchTerms: [50, 63], shouldTriggerQuery: true });
+ });
+
+ it('should change minValue to a lower value when it is to close to maxValue and "stopGapBetweenSliderHandles" is enabled so it will auto-change minValue to a lower value plus gap', () => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+ const minVal = 56;
+ const maxVal = 58;
+
+ mockColumn.filter = {
+ filterOptions: { stopGapBetweenSliderHandles: 5 }
+ };
+ filter.init(filterArguments);
+ filter.setValues([minVal, maxVal]);
+ const sliderInputs = divContainer.querySelectorAll('.slider-filter-input');
+ const filterElms = divContainer.querySelectorAll('.search-filter.slider-container.filter-duration input');
+ filterElms[0].dispatchEvent(new CustomEvent('change'));
+
+ expect(sliderInputs[0].value).toBe(`${minVal - 5}`);
+ expect(sliderInputs[1].value).toBe('58');
+ expect(spyCallback).toHaveBeenLastCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'RangeInclusive', searchTerms: [51, 58], shouldTriggerQuery: true });
+ });
+
+ it('should change maxValue to a lower value when it is to close to minValue and "stopGapBetweenSliderHandles" is enabled so it will auto-change maxValue to a lower value plus gap', () => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+ const minVal = 56;
+ const maxVal = 58;
+
+ mockColumn.filter = {
+ filterOptions: { stopGapBetweenSliderHandles: 5 }
+ };
+ filter.init(filterArguments);
+ filter.setValues([minVal, maxVal]);
+ const sliderInputs = divContainer.querySelectorAll('.slider-filter-input');
+ const filterElms = divContainer.querySelectorAll('.search-filter.slider-container.filter-duration input');
+ filterElms[1].dispatchEvent(new CustomEvent('change'));
+
+ expect(sliderInputs[0].value).toBe('56');
+ expect(sliderInputs[1].value).toBe(`${minVal + 5}`);
+ expect(spyCallback).toHaveBeenLastCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'RangeInclusive', searchTerms: [56, 61], shouldTriggerQuery: true });
+ });
+
+ it('should be able to call "setValues" and set empty values and the input to not have the "filled" css class', () => {
+ filter.init(filterArguments);
+ filter.setValues([3, 80]);
+ let filledInputElm = divContainer.querySelector('.search-filter.slider-container.filter-duration.filled') as HTMLInputElement;
+
+ expect(filledInputElm).toBeTruthy();
+
+ filter.setValues('');
+ filledInputElm = divContainer.querySelector('.search-filter.slider-container.filter-duration.filled') as HTMLInputElement;
+ expect(filledInputElm).toBeFalsy();
+ });
+
+ it('should create the input filter with default search terms range when passed as a filter argument', () => {
+ filterArguments.searchTerms = [3, 80];
+
+ filter.init(filterArguments);
+
+ const filterLowestElm = divContainer.querySelector('.lowest-range-duration') as HTMLInputElement;
+ const filterHighestElm = divContainer.querySelector('.highest-range-duration') as HTMLInputElement;
+
+ expect(filterLowestElm.textContent).toBe('3');
+ expect(filterHighestElm.textContent).toBe('80');
+ expect(filter.currentValues).toEqual([3, 80]);
+ });
+
+ it('should create the input filter with min/max slider values being set by filter "minValue" and "maxValue"', () => {
+ mockColumn.filter = {
+ minValue: 4,
+ maxValue: 69,
+ };
+
+ filter.init(filterArguments);
+
+ const filterLowestElm = divContainer.querySelector('.lowest-range-duration') as HTMLInputElement;
+ const filterHighestElm = divContainer.querySelector('.highest-range-duration') as HTMLInputElement;
+
+ expect(filterLowestElm.textContent).toBe('4');
+ expect(filterHighestElm.textContent).toBe('69');
+ expect(filter.currentValues).toEqual([4, 69]);
+ });
+
+ it('should create the input filter with min/max slider values being set by filter "sliderStartValue" and "sliderEndValue" through the filterOptions', () => {
+ mockColumn.filter = {
+ filterOptions: {
+ sliderStartValue: 4,
+ sliderEndValue: 69,
+ }
+ };
+
+ filter.init(filterArguments);
+
+ const filterLowestElm = divContainer.querySelector('.lowest-range-duration') as HTMLInputElement;
+ const filterHighestElm = divContainer.querySelector('.highest-range-duration') as HTMLInputElement;
+
+ expect(filterLowestElm.textContent).toBe('4');
+ expect(filterHighestElm.textContent).toBe('69');
+ expect(filter.currentValues).toEqual([4, 69]);
+ });
+
+ it('should create the input filter with min/max slider values defined in params and expect deprecated console warning', () => {
+ mockColumn.filter = {
+ params: {
+ sliderStartValue: 4,
+ sliderEndValue: 69,
+ }
+ };
+
+ filter.init(filterArguments);
+
+ const filterLowestElm = divContainer.querySelector('.lowest-range-duration') as HTMLInputElement;
+ const filterHighestElm = divContainer.querySelector('.highest-range-duration') as HTMLInputElement;
+
+ expect(consoleSpy).toHaveBeenCalledWith('[Slickgrid-Universal] All filter.params were moved, and deprecated, to "filterOptions" as SliderRangeOption for better typing support.');
+ expect(filterLowestElm.textContent).toBe('4');
+ expect(filterHighestElm.textContent).toBe('69');
+ expect(filter.currentValues).toEqual([4, 69]);
+ });
+
+ it('should create the input filter with default search terms range but without showing side numbers when "hideSliderNumbers" is set in filterOptions', () => {
+ filterArguments.searchTerms = [3, 80];
+ mockColumn.filter!.filterOptions = { hideSliderNumbers: true };
+
+ filter.init(filterArguments);
+
+ const filterLowestElms = divContainer.querySelectorAll('.lowest-range-duration');
+ const filterHighestElms = divContainer.querySelectorAll('.highest-range-duration');
+
+ expect(filterLowestElms.length).toBe(0);
+ expect(filterHighestElms.length).toBe(0);
+ expect(filter.currentValues).toEqual([3, 80]);
+ });
+
+ it('should trigger a callback with the clear filter set when calling the "clear" method', () => {
+ filterArguments.searchTerms = [3, 80];
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filter.init(filterArguments);
+ filter.clear();
+
+ expect(filter.currentValues).toEqual([0, 100]);
+ expect(spyCallback).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: true });
+ });
+
+ it('should trigger a callback with the clear filter but without querying when when calling the "clear" method with False as argument', () => {
+ filterArguments.searchTerms = [3, 80];
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filter.init(filterArguments);
+ filter.clear(false);
+
+ expect(filter.currentValues).toEqual([0, 100]);
+ expect(spyCallback).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: false });
+ });
+
+ it('should trigger a callback with the clear filter set when calling the "clear" method and expect min/max slider values being with values of "sliderStartValue" and "sliderEndValue" when defined through the filterOptions', () => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+ mockColumn.filter = {
+ filterOptions: {
+ sliderStartValue: 4,
+ sliderEndValue: 69,
+ }
+ };
+
+ filter.init(filterArguments);
+ filter.clear(false);
+
+ expect(filter.currentValues).toEqual([4, 69]);
+ expect(spyCallback).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: false });
+ });
+
+ it('should enableSliderTrackColoring and trigger a change event and expect slider track to have background color', () => {
+ mockColumn.filter = { filterOptions: { enableSliderTrackColoring: true } };
+ filter.init(filterArguments);
+ filter.setValues(['2..80']);
+ const filterElms = divContainer.querySelectorAll('.search-filter.slider-container.filter-duration input');
+ filterElms[0].dispatchEvent(new CustomEvent('change'));
+ const sliderTrackElm = divContainer.querySelector('.slider-track') as HTMLDivElement;
+
+ // expect(sliderTrackElm.style.background).toBe('linear-gradient(to right, #eee 2%, var(--slick-slider-filter-thumb-color, #86bff8) 2%, var(--slick-slider-filter-thumb-color, #86bff8) 80%, #eee 80%)');
+ expect(filter.sliderRangeOptions?.sliderTrackBackground).toBe('linear-gradient(to right, #eee 2%, var(--slick-slider-filter-thumb-color, #86bff8) 2%, var(--slick-slider-filter-thumb-color, #86bff8) 80%, #eee 80%)');
+ });
+
+ it('should click on the slider track and expect left handle to move to the new position when calculated percent is below 50%', () => {
+ filter.init(filterArguments);
+ const sliderInputs = divContainer.querySelectorAll('.slider-filter-input');
+ const sliderTrackElm = divContainer.querySelector('.slider-track') as HTMLDivElement;
+
+ const sliderOneChangeSpy = jest.spyOn(sliderInputs[0], 'dispatchEvent');
+ const sliderTwoChangeSpy = jest.spyOn(sliderInputs[1], 'dispatchEvent');
+
+ const clickEvent = new Event('click');
+ Object.defineProperty(clickEvent, 'offsetX', { writable: true, configurable: true, value: 22 });
+ Object.defineProperty(sliderTrackElm, 'offsetWidth', { writable: true, configurable: true, value: 85 });
+ sliderTrackElm.dispatchEvent(clickEvent);
+
+ expect(sliderOneChangeSpy).toHaveBeenCalled();
+ expect(sliderTwoChangeSpy).not.toHaveBeenCalled();
+ });
+
+ it('should click on the slider track and expect right handle to move to the new position when calculated percent is above 50%', () => {
+ filter.init(filterArguments);
+ const sliderInputs = divContainer.querySelectorAll('.slider-filter-input');
+ const sliderTrackElm = divContainer.querySelector('.slider-track') as HTMLDivElement;
+
+ const sliderOneChangeSpy = jest.spyOn(sliderInputs[0], 'dispatchEvent');
+ const sliderTwoChangeSpy = jest.spyOn(sliderInputs[1], 'dispatchEvent');
+
+ const clickEvent = new Event('click');
+ Object.defineProperty(clickEvent, 'offsetX', { writable: true, configurable: true, value: 56 });
+ Object.defineProperty(sliderTrackElm, 'offsetWidth', { writable: true, configurable: true, value: 75 });
+ sliderTrackElm.dispatchEvent(clickEvent);
+
+ expect(sliderOneChangeSpy).not.toHaveBeenCalled();
+ expect(sliderTwoChangeSpy).toHaveBeenCalled();
+ });
+});
\ No newline at end of file
diff --git a/packages/common/src/filters/filters.index.ts b/packages/common/src/filters/filters.index.ts
index c98595f91..1be909ffc 100644
--- a/packages/common/src/filters/filters.index.ts
+++ b/packages/common/src/filters/filters.index.ts
@@ -13,6 +13,7 @@ import { NativeSelectFilter } from './nativeSelectFilter';
import { DateRangeFilter } from './dateRangeFilter';
import { SingleSelectFilter } from './singleSelectFilter';
import { SliderFilter } from './sliderFilter';
+import { SliderRangeFilter } from './sliderRangeFilter';
export const Filters = {
/** AutoComplete Filter (using https://github.com/kraaden/autocomplete) */
@@ -66,6 +67,9 @@ export const Filters = {
/** Single Select filter, which uses 3rd party lib "multiple-select.js" */
singleSelect: SingleSelectFilter,
- /** Slider Filter (only 1 value) */
+ /** Slider Filter (single value) */
slider: SliderFilter,
+
+ /** Slider Range Filter (dual values, lowest/highest filter range) */
+ sliderRange: SliderRangeFilter,
};
diff --git a/packages/common/src/filters/sliderRangeFilter.ts b/packages/common/src/filters/sliderRangeFilter.ts
new file mode 100644
index 000000000..08576b80c
--- /dev/null
+++ b/packages/common/src/filters/sliderRangeFilter.ts
@@ -0,0 +1,421 @@
+import { toSentenceCase } from '@slickgrid-universal/utils';
+
+import { Constants } from '../constants';
+import { OperatorString, OperatorType, SearchTerm, } from '../enums/index';
+import {
+ Column,
+ ColumnFilter,
+ Filter,
+ FilterArguments,
+ FilterCallback,
+ GridOption,
+ SlickGrid,
+ SliderRangeOption,
+} from '../interfaces/index';
+import { BindingEventService } from '../services/bindingEvent.service';
+import { createDomElement, emptyElement } from '../services/domUtilities';
+
+interface CurrentSliderOption {
+ minValue: number;
+ maxValue: number;
+ step: number;
+ sliderTrackBackground?: string;
+}
+
+const GAP_BETWEEN_SLIDER_HANDLES = 0;
+const Z_INDEX_MIN_GAP = 20; // gap in Px before we change z-index so that lowest/highest handle doesn't block each other
+
+/** A Slider Range Filter written in pure JS, this is only meant to be used as a range filter (with 2 handles lowest & highest values) */
+export class SliderRangeFilter implements Filter {
+ protected _bindEventService: BindingEventService;
+ protected _clearFilterTriggered = false;
+ protected _currentValues?: number[];
+ protected _shouldTriggerQuery = true;
+ protected _sliderOptions!: CurrentSliderOption;
+ protected filterElm!: HTMLDivElement;
+ protected _argFilterContainerElm!: HTMLDivElement;
+ protected _divContainerFilterElm!: HTMLDivElement;
+ protected _filterContainerElm!: HTMLDivElement;
+ protected _lowestSliderNumberElm?: HTMLSpanElement;
+ protected _highestSliderNumberElm?: HTMLSpanElement;
+ protected _sliderRangeContainElm!: HTMLDivElement;
+ protected _sliderTrackElm!: HTMLDivElement;
+ protected _sliderOneElm!: HTMLInputElement;
+ protected _sliderTwoElm!: HTMLInputElement;
+ grid!: SlickGrid;
+ searchTerms: SearchTerm[] = [];
+ columnDef!: Column;
+ callback!: FilterCallback;
+
+ constructor() {
+ this._bindEventService = new BindingEventService();
+ }
+
+ /** @deprecated Getter for the Filter Generic Params */
+ protected get filterParams(): any {
+ return this.columnDef?.filter?.params ?? {};
+ }
+
+ /** Getter for the Filter Options */
+ get filterOptions(): SliderRangeOption | undefined {
+ return this.columnFilter.filterOptions;
+ }
+
+
+ /** Getter for the `filter` properties */
+ protected get filterProperties(): ColumnFilter {
+ return this.columnDef && this.columnDef.filter || {};
+ }
+
+ /** Getter for the Column Filter */
+ get columnFilter(): ColumnFilter {
+ return this.columnDef && this.columnDef.filter || {};
+ }
+
+ /** Getter for the Current Slider Values */
+ get currentValues(): number[] | undefined {
+ return this._currentValues;
+ }
+
+ /** Getter to know what would be the default operator when none is specified */
+ get defaultOperator(): OperatorType | OperatorString {
+ return this.gridOptions.defaultFilterRangeOperator || OperatorType.rangeInclusive;
+ }
+
+ /** Getter for the Grid Options pulled through the Grid Object */
+ get gridOptions(): GridOption {
+ return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {};
+ }
+
+ /** Getter for the current Slider Options */
+ get sliderRangeOptions(): CurrentSliderOption | undefined {
+ return this._sliderOptions;
+ }
+
+ /** Getter of the Operator to use when doing the filter comparing */
+ get operator(): OperatorType | OperatorString {
+ return this.columnFilter?.operator ?? this.defaultOperator;
+ }
+
+ /** Setter for the filter operator */
+ set operator(operator: OperatorType | OperatorString) {
+ if (this.columnFilter) {
+ this.columnFilter.operator = operator;
+ }
+ }
+
+ /**
+ * Initialize the Filter
+ */
+ init(args: FilterArguments) {
+ if (!args) {
+ throw new Error('[Slickgrid-Universal] A filter must always have an "init()" with valid arguments.');
+ }
+ this.grid = args.grid;
+ this.callback = args.callback;
+ this.columnDef = args.columnDef;
+ this.searchTerms = args?.searchTerms ?? [];
+ this._argFilterContainerElm = args.filterContainerElm;
+
+ // step 1, create the DOM Element of the filter & initialize it if searchTerm is filled
+ this.filterElm = this.createDomFilterElement(this.searchTerms);
+ }
+
+ /**
+ * Clear the filter value
+ */
+ clear(shouldTriggerQuery = true) {
+ if (this.filterElm) {
+ this._clearFilterTriggered = true;
+ this._shouldTriggerQuery = shouldTriggerQuery;
+ this.searchTerms = [];
+ const lowestValue = (this.getFilterOptionByName('sliderStartValue') ?? Constants.SLIDER_DEFAULT_MIN_VALUE) as number;
+ const highestValue = (this.getFilterOptionByName('sliderEndValue') ?? Constants.SLIDER_DEFAULT_MAX_VALUE) as number;
+ this._currentValues = [lowestValue, highestValue];
+ this._sliderOneElm.value = `${lowestValue}`;
+ this._sliderTwoElm.value = `${highestValue}`;
+ this.dispatchBothEvents();
+
+ if (!this.getFilterOptionByName('hideSliderNumbers')) {
+ this.renderSliderValues(lowestValue, highestValue);
+ }
+ this._divContainerFilterElm.classList.remove('filled');
+ this.filterElm.classList.remove('filled');
+ this.callback(undefined, { columnDef: this.columnDef, clearFilterTriggered: true, shouldTriggerQuery });
+ }
+ }
+
+ /**
+ * destroy the filter
+ */
+ destroy() {
+ this._bindEventService.unbindAll();
+ }
+
+ /**
+ * Get option from filter.params PR filter.filterOptions
+ * @deprecated this should be removed when slider filterParams are replaced by filterOptions
+ */
+ getFilterOptionByName(optionName: string, defaultValue?: string | number | boolean): T {
+ let outValue: string | number | boolean | undefined;
+ if (this.filterOptions?.[optionName as keyof SliderRangeOption] !== undefined) {
+ outValue = this.filterOptions[optionName as keyof SliderRangeOption];
+ } else if (this.filterParams?.[optionName] !== undefined) {
+ console.warn('[Slickgrid-Universal] All filter.params were moved, and deprecated, to "filterOptions" as SliderRangeOption for better typing support.');
+ outValue = this.filterParams?.[optionName];
+ }
+ return outValue as T ?? defaultValue ?? undefined;
+ }
+
+ /**
+ * Render both slider values (low/high) on screen
+ * @param lowestValue number
+ * @param highestValue number
+ */
+ renderSliderValues(lowestValue: number | string, highestValue: number | string) {
+ if (this._lowestSliderNumberElm?.textContent) {
+ this._lowestSliderNumberElm.textContent = lowestValue.toString();
+ }
+ if (this._highestSliderNumberElm?.textContent) {
+ this._highestSliderNumberElm.textContent = highestValue.toString();
+ }
+ }
+
+ getValues() {
+ return this._currentValues;
+ }
+
+ /**
+ * Set value(s) on the DOM element
+ * @params searchTerms
+ */
+ setValues(searchTerms: SearchTerm | SearchTerm[], operator?: OperatorType | OperatorString) {
+ if (searchTerms) {
+ let sliderValues: number[] | string[] = [];
+
+ // get the slider values, if it's a string with the "..", we'll do the split else we'll use the array of search terms
+ if (typeof searchTerms === 'string' || (Array.isArray(searchTerms) && typeof searchTerms[0] === 'string') && (searchTerms[0] as string).indexOf('..') > 0) {
+ sliderValues = (typeof searchTerms === 'string') ? [(searchTerms as string)] : (searchTerms[0] as string).split('..');
+ } else if (Array.isArray(searchTerms)) {
+ sliderValues = searchTerms as string[];
+ }
+
+ if (Array.isArray(sliderValues) && sliderValues.length === 2) {
+ if (!this.getFilterOptionByName('hideSliderNumbers')) {
+ const [lowestValue, highestValue] = sliderValues;
+ this._sliderOneElm.value = String(lowestValue ?? Constants.SLIDER_DEFAULT_MIN_VALUE);
+ this._sliderTwoElm.value = String(highestValue ?? Constants.SLIDER_DEFAULT_MAX_VALUE);
+ this.renderSliderValues(sliderValues[0], sliderValues[1]);
+ }
+ }
+ }
+
+ (searchTerms && (this.getValues?.() ?? []).length > 0)
+ ? this.filterElm.classList.add('filled')
+ : this.filterElm.classList.remove('filled');
+
+ // set the operator when defined
+ this.operator = operator || this.defaultOperator;
+ }
+
+ /**
+ * Create the Filter DOM element
+ * Follows article with few modifications (without tooltip & neither slider track color)
+ * https://codingartistweb.com/2021/06/double-range-slider-html-css-javascript/
+ * @param searchTerm optional preset search terms
+ */
+ protected createDomFilterElement(searchTerms?: SearchTerm | SearchTerm[]) {
+ const columnId = this.columnDef?.id ?? '';
+ const minValue = +(this.filterProperties?.minValue ?? Constants.SLIDER_DEFAULT_MIN_VALUE);
+ const maxValue = +(this.filterProperties?.maxValue ?? Constants.SLIDER_DEFAULT_MAX_VALUE);
+ const step = +(this.filterProperties?.valueStep ?? Constants.SLIDER_DEFAULT_STEP);
+ emptyElement(this._argFilterContainerElm);
+
+ let defaultStartValue: number = Constants.SLIDER_DEFAULT_MIN_VALUE;
+ let defaultEndValue: number = Constants.SLIDER_DEFAULT_MAX_VALUE;
+ if (Array.isArray(searchTerms) && searchTerms.length > 1) {
+ defaultStartValue = +searchTerms[0];
+ defaultEndValue = +searchTerms[1];
+ } else {
+ defaultStartValue = +(this.getFilterOptionByName('sliderStartValue') ?? minValue);
+ defaultEndValue = +(this.getFilterOptionByName('sliderEndValue') ?? maxValue);
+ }
+
+ this._sliderRangeContainElm = createDomElement('div', { className: `filter-input filter-${columnId} slider-range-container slider-values` });
+ this._sliderRangeContainElm.title = `${defaultStartValue} - ${defaultEndValue}`;
+
+ this._sliderTrackElm = createDomElement('div', { className: 'slider-track' });
+ this._sliderOneElm = createDomElement('input', {
+ type: 'range',
+ className: `slider-filter-input`,
+ ariaLabel: this.columnFilter?.ariaLabel ?? `${toSentenceCase(columnId + '')} Search Filter`,
+ defaultValue: `${defaultStartValue}`, value: `${defaultStartValue}`,
+ min: `${minValue}`, max: `${maxValue}`, step: `${step}`,
+ });
+ this._sliderTwoElm = createDomElement('input', {
+ type: 'range',
+ className: `slider-filter-input`,
+ ariaLabel: this.columnFilter?.ariaLabel ?? `${toSentenceCase(columnId + '')} Search Filter`,
+ defaultValue: `${defaultEndValue}`, value: `${defaultEndValue}`,
+ min: `${minValue}`, max: `${maxValue}`, step: `${step}`,
+ });
+
+ this._bindEventService.bind(this._sliderTrackElm, 'click', this.sliderTrackClicked.bind(this) as EventListener);
+ this._bindEventService.bind(this._sliderOneElm, ['input', 'change'], this.slideOneInputChanged.bind(this));
+ this._bindEventService.bind(this._sliderTwoElm, ['input', 'change'], this.slideTwoInputChanged.bind(this));
+ this._bindEventService.bind(this._sliderOneElm, ['change', 'mouseup', 'touchend'], this.onValueChanged.bind(this) as EventListener);
+ this._bindEventService.bind(this._sliderTwoElm, ['change', 'mouseup', 'touchend'], this.onValueChanged.bind(this) as EventListener);
+
+ // create the DOM element
+ const sliderNumberClass = this.getFilterOptionByName('hideSliderNumbers') ? '' : 'input-group';
+ this._divContainerFilterElm = createDomElement('div', { className: `${sliderNumberClass} search-filter slider-container slider-values filter-${columnId}`.trim() });
+
+ this._sliderRangeContainElm.append(this._sliderTrackElm);
+ this._sliderRangeContainElm.append(this._sliderOneElm);
+ this._sliderRangeContainElm.append(this._sliderTwoElm);
+
+ if (this.getFilterOptionByName('hideSliderNumbers')) {
+ this._divContainerFilterElm.append(this._sliderRangeContainElm);
+ } else {
+ const lowestSliderContainerDivElm = createDomElement('div', { className: `input-group-addon input-group-prepend slider-range-value` });
+ this._lowestSliderNumberElm = createDomElement('span', { className: `input-group-text lowest-range-${columnId}`, textContent: `${defaultStartValue}` });
+ lowestSliderContainerDivElm.append(this._lowestSliderNumberElm);
+
+ const highestSliderContainerDivElm = createDomElement('div', { className: `input-group-addon input-group-append slider-range-value` });
+ this._highestSliderNumberElm = createDomElement('span', { className: `input-group-text highest-range-${columnId}`, textContent: `${defaultEndValue}` });
+ highestSliderContainerDivElm.append(this._highestSliderNumberElm);
+
+ this._divContainerFilterElm.append(lowestSliderContainerDivElm);
+ this._divContainerFilterElm.append(this._sliderRangeContainElm);
+ this._divContainerFilterElm.append(highestSliderContainerDivElm);
+ }
+
+ // if we are preloading searchTerms, we'll keep them for reference
+ this._currentValues = [defaultStartValue, defaultEndValue];
+
+ // merge options with optional user's custom options
+ this._sliderOptions = { minValue, maxValue, step };
+
+ // if there's a search term, we will add the "filled" class for styling purposes
+ if (Array.isArray(searchTerms) && searchTerms.length > 0 && searchTerms[0] !== '') {
+ this._divContainerFilterElm.classList.add('filled');
+ }
+
+ // append the new DOM element to the header row
+ this._argFilterContainerElm.append(this._divContainerFilterElm);
+ this.updateTrackFilledColor();
+
+ return this._divContainerFilterElm;
+ }
+
+ protected dispatchBothEvents() {
+ this._sliderOneElm.dispatchEvent(new Event('change'));
+ this._sliderTwoElm.dispatchEvent(new Event('change'));
+ }
+
+ /** handle value change event triggered, trigger filter callback & update "filled" class name */
+ protected onValueChanged(e: Event) {
+ const sliderOneVal = parseInt(this._sliderOneElm.value, 10);
+ const sliderTwoVal = parseInt(this._sliderTwoElm.value, 10);
+ const values = [sliderOneVal, sliderTwoVal];
+ const value = values.join('..');
+
+ if (this._clearFilterTriggered) {
+ this.filterElm.classList.remove('filled');
+ this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery });
+ } else {
+ value === '' ? this.filterElm.classList.remove('filled') : this.filterElm.classList.add('filled');
+ this.callback(e, { columnDef: this.columnDef, operator: this.operator, searchTerms: values, shouldTriggerQuery: this._shouldTriggerQuery });
+ }
+ // reset both flags for next use
+ this._clearFilterTriggered = false;
+ this._shouldTriggerQuery = true;
+ this.changeBothSliderFocuses(false);
+
+ // trigger leave event to avoid having previous value still being displayed with custom tooltip feat
+ this.grid?.onHeaderMouseLeave.notify({ column: this.columnDef, grid: this.grid });
+ }
+
+ protected changeBothSliderFocuses(isAddingFocus: boolean) {
+ const addRemoveCmd = isAddingFocus ? 'add' : 'remove';
+ this._sliderOneElm.classList[addRemoveCmd]('focus');
+ this._sliderTwoElm.classList[addRemoveCmd]('focus');
+ }
+
+ protected slideOneInputChanged() {
+ const sliderOneVal = parseInt(this._sliderOneElm.value, 10);
+ const sliderTwoVal = parseInt(this._sliderTwoElm.value, 10);
+
+ if (sliderTwoVal - sliderOneVal <= this.getFilterOptionByName('stopGapBetweenSliderHandles', GAP_BETWEEN_SLIDER_HANDLES)) {
+ this._sliderOneElm.value = String(sliderOneVal - this.getFilterOptionByName('stopGapBetweenSliderHandles', GAP_BETWEEN_SLIDER_HANDLES));
+ }
+
+ this._sliderRangeContainElm.title = `${sliderOneVal} - ${sliderTwoVal}`;
+
+ // change which handle has higher z-index to make them still usable,
+ // ie when left handle reaches the end, it has to have higher z-index or else it will be stuck below
+ // and we cannot move right because it cannot go below min value
+ if (+this._sliderOneElm.value >= +this._sliderTwoElm.value - Z_INDEX_MIN_GAP) {
+ this._sliderOneElm.style.zIndex = '1';
+ this._sliderTwoElm.style.zIndex = '0';
+ } else {
+ this._sliderOneElm.style.zIndex = '0';
+ this._sliderTwoElm.style.zIndex = '1';
+ }
+
+ this.updateTrackFilledColor();
+ this.changeBothSliderFocuses(true);
+ if (!this.getFilterOptionByName('hideSliderNumbers') && this._lowestSliderNumberElm?.textContent) {
+ this._lowestSliderNumberElm.textContent = this._sliderOneElm.value;
+ }
+
+ }
+
+ protected slideTwoInputChanged() {
+ const sliderOneVal = parseInt(this._sliderOneElm.value, 10);
+ const sliderTwoVal = parseInt(this._sliderTwoElm.value, 10);
+
+ if (sliderTwoVal - sliderOneVal <= this.getFilterOptionByName('stopGapBetweenSliderHandles', GAP_BETWEEN_SLIDER_HANDLES)) {
+ this._sliderTwoElm.value = String(sliderOneVal + this.getFilterOptionByName('stopGapBetweenSliderHandles', GAP_BETWEEN_SLIDER_HANDLES));
+ }
+
+ this.updateTrackFilledColor();
+ this.changeBothSliderFocuses(true);
+ this._sliderRangeContainElm.title = `${sliderOneVal} - ${sliderTwoVal}`;
+
+ if (!this.getFilterOptionByName('hideSliderNumbers') && this._highestSliderNumberElm?.textContent) {
+ this._highestSliderNumberElm.textContent = this._sliderTwoElm.value;
+ }
+ }
+
+ protected sliderTrackClicked(e: MouseEvent) {
+ e.preventDefault();
+ const sliderTrackX = e.offsetX;
+ const sliderTrackWidth = this._sliderTrackElm.offsetWidth;
+ const trackPercentPosition = (sliderTrackX + 0) * 100 / sliderTrackWidth;
+
+ // when tracker position is below 50% we'll auto-place the left slider thumb or else auto-place right slider thumb
+ if (trackPercentPosition <= 50) {
+ this._sliderOneElm.value = `${trackPercentPosition}`;
+ this._sliderOneElm.dispatchEvent(new Event('change'));
+ } else {
+ this._sliderTwoElm.value = `${trackPercentPosition}`;
+ this._sliderTwoElm.dispatchEvent(new Event('change'));
+ }
+ }
+
+ protected updateTrackFilledColor() {
+ if (this.getFilterOptionByName('enableSliderTrackColoring')) {
+ const percent1 = ((+this._sliderOneElm.value - +this._sliderOneElm.min) / (this.sliderRangeOptions?.maxValue ?? 0 - +this._sliderOneElm.min)) * 100;
+ const percent2 = ((+this._sliderTwoElm.value - +this._sliderTwoElm.min) / (this.sliderRangeOptions?.maxValue ?? 0 - +this._sliderTwoElm.min)) * 100;
+ const bg = 'linear-gradient(to right, %b %p1, %c %p1, %c %p2, %b %p2)'
+ .replace(/%b/g, '#eee')
+ .replace(/%c/g, (this.getFilterOptionByName('sliderTrackFilledColor') ?? 'var(--slick-slider-filter-thumb-color, #86bff8)') as string)
+ .replace(/%p1/g, `${percent1}%`)
+ .replace(/%p2/g, `${percent2}%`);
+
+ this._sliderTrackElm.style.background = bg;
+ this._sliderOptions.sliderTrackBackground = bg;
+ }
+ }
+}
diff --git a/packages/common/src/interfaces/index.ts b/packages/common/src/interfaces/index.ts
index 71b796561..a18fe475a 100644
--- a/packages/common/src/interfaces/index.ts
+++ b/packages/common/src/interfaces/index.ts
@@ -146,6 +146,7 @@ export * from './slickRange.interface';
export * from './slickRemoteModel.interface';
export * from './slickResizer.interface';
export * from './slickRowDetailView.interface';
+export * from './sliderOption.interface';
export * from './sorter.interface';
export * from './textExportOption.interface';
export * from './treeDataOption.interface';
diff --git a/packages/common/src/interfaces/sliderOption.interface.ts b/packages/common/src/interfaces/sliderOption.interface.ts
new file mode 100644
index 000000000..c8013befc
--- /dev/null
+++ b/packages/common/src/interfaces/sliderOption.interface.ts
@@ -0,0 +1,24 @@
+export interface SliderOption {
+ /** Defaults to true, hide the slider number shown on the right side */
+ hideSliderNumber?: boolean;
+
+ /** Slider max end value */
+ sliderEndValue?: number;
+
+ /** Slider min start value */
+ sliderStartValue?: number;
+}
+
+export interface SliderRangeOption extends Omit {
+ /** Defaults to false, do we want to show slider track coloring? */
+ enableSliderTrackColoring?: boolean;
+
+ /** Defaults to false, hide the slider numbers shown on the left/right side */
+ hideSliderNumbers?: boolean;
+
+ /** Defaults to 0, minimum value gap before reaching the maximum end value */
+ stopGapBetweenSliderHandles?: number;
+
+ /** Defaults to "#3C97DD", what will be the color to use to represent slider range */
+ sliderTrackFilledColor?: string;
+}
\ No newline at end of file
diff --git a/packages/common/src/styles/_variables-theme-salesforce.scss b/packages/common/src/styles/_variables-theme-salesforce.scss
index e21eff9b6..4ad3dcfbf 100644
--- a/packages/common/src/styles/_variables-theme-salesforce.scss
+++ b/packages/common/src/styles/_variables-theme-salesforce.scss
@@ -178,7 +178,7 @@ $slick-editor-modal-title-font-weight: var(--lwc-fontWeig
$slick-editor-modal-title-line-height: var(--lwc-lineHeightHeading, 1.25) !default;
$slick-editor-modal-title-text-align: center !default;
$slick-large-editor-button-border-radius: 3px !default;
-$slick-slider-filter-thumb-color: #3C97DD !default;
+$slick-slider-filter-thumb-color: #6cb6ff !default;
$slick-slider-filter-runnable-track-bgcolor: #ECEBEA !default;
$slick-row-selected-color: #ECEBEA !default;
$slick-row-highlight-background-color: lighten($slick-highlight-color, 50%) !default;
diff --git a/packages/common/src/styles/_variables.scss b/packages/common/src/styles/_variables.scss
index 76a66601c..0ddf5edeb 100644
--- a/packages/common/src/styles/_variables.scss
+++ b/packages/common/src/styles/_variables.scss
@@ -659,37 +659,27 @@ $slick-draggable-group-column-icon-margin-left: 4px !default;
$slick-slider-filter-border: 1px solid #ccc !default;
$slick-slider-filter-bgcolor: #eee !default;
$slick-slider-filter-runnable-track-bgcolor: #ddd !default;
-$slick-slider-filter-runnable-track-height: 4px !default;
+$slick-slider-filter-runnable-track-cursor: pointer !default;
+$slick-slider-filter-runnable-track-height: 5px !default;
$slick-slider-filter-runnable-track-padding: 0 6px !default;
$slick-slider-filter-fill-lower-color: #ddd !default; /* ms only */
$slick-slider-filter-fill-focus-lower-color: #aaa !default; /* ms only */
$slick-slider-filter-height: $slick-header-input-height !default;
$slick-slider-filter-thumb-border-radius: 50% !default;
-$slick-slider-filter-thumb-cursor: pointer !default;
+$slick-slider-filter-thumb-cursor: grab !default;
$slick-slider-filter-thumb-color: rgb(201, 219, 203) !default;
+$slick-slider-filter-thumb-active-bg-color: #fff !default;
$slick-slider-filter-thumb-size: 14px !default;
-$slick-slider-filter-thumb-height: calc(#{$slick-slider-filter-thumb-size} - 2px) !default;
+$slick-slider-filter-thumb-height: calc(#{$slick-slider-filter-thumb-size} - 4px) !default;
$slick-slider-filter-thumb-width: $slick-slider-filter-thumb-height !default;
-$slick-slider-filter-thumb-border: 1px solid darken($slick-slider-filter-thumb-color, 15%) !default;
+$slick-slider-filter-thumb-border: 2px solid darken($slick-slider-filter-thumb-color, 15%) !default;
$slick-slider-filter-number-padding: 4px 8px !default;
$slick-slider-filter-number-font-size: calc(#{$slick-font-size-base-value} - 1px) !default;
-/* Input Range Slider Filter (with jQuery UI) */
-$slick-slider-range-filter-height: $slick-slider-filter-height !default;
-$slick-slider-range-filter-border: $slick-slider-filter-border !default;
-$slick-slider-range-filter-thumb-color: $slick-slider-filter-thumb-color !default;
-$slick-slider-range-filter-thumb-border: $slick-slider-filter-thumb-border !default;
-$slick-slider-range-filter-thumb-border-radius: $slick-slider-filter-thumb-border-radius !default;
-$slick-slider-range-filter-thumb-cursor: $slick-slider-filter-thumb-cursor !default;
-$slick-slider-range-filter-thumb-size: $slick-slider-filter-thumb-size !default;
-$slick-slider-range-filter-thumb-top: -5px !default;
-$slick-slider-range-filter-runnable-track-top: 45% !default;
-$slick-slider-range-filter-runnable-track-height: $slick-slider-filter-runnable-track-height !default;
-$slick-slider-range-filter-bgcolor: $slick-slider-filter-bgcolor !default;
-$slick-slider-range-filter-padding: 0 12px !default;
-$slick-slider-range-filter-values-slider-width: calc(98% - 16px) !default;
-$slick-slider-range-filter-values-slider-top: 12px !default;
-$slick-slider-range-filter-values-slider-margin: 0 10px !default;
+/* Input Range Slider Filter */
+$slick-slider-range-filter-border-radius: 4px !default;
+$slick-slider-range-focus-border-color: lighten($slick-primary-color, 10%) !default;
+$slick-slider-range-focus-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px rgba(lighten($slick-primary-color, 3%), .3) !default;
/* Multiple-Select Filter */
$slick-multiselect-input-filter-border: 1px solid #ccc !default;
diff --git a/packages/common/src/styles/slick-filters.scss b/packages/common/src/styles/slick-filters.scss
index 050875b9c..5cb6a2ebf 100644
--- a/packages/common/src/styles/slick-filters.scss
+++ b/packages/common/src/styles/slick-filters.scss
@@ -9,26 +9,27 @@ $slick-filled-filter-font-weight: 400 !default;
.slick-headerrow {
input.search-filter.filled,
- .search-filter.filled input,
- .search-filter.filled input.flatpickr-input,
- .search-filter.filled .input-group-addon.slider-value,
- .search-filter.filled .input-group-addon select {
- color: var(--slick-filled-filter-color, $slick-filled-filter-color);
- font-weight: var(--slick-filled-filter-font-weight, $slick-filled-filter-font-weight);
- border: var(--slick-filled-filter-border, $slick-filled-filter-border);
+ .search-filter.filled input,
+ .search-filter.filled input.flatpickr-input,
+ .search-filter.filled .input-group-addon.slider-value,
+ .search-filter.filled .input-group-addon.slider-range-value,
+ .search-filter.filled .input-group-addon select {
+ color: var(--slick-filled-filter-color, $slick-filled-filter-color);
+ font-weight: var(--slick-filled-filter-font-weight, $slick-filled-filter-font-weight);
+ border: var(--slick-filled-filter-border, $slick-filled-filter-border);
+ box-shadow: var(--slick-filled-filter-box-shadow, $slick-filled-filter-box-shadow);
+ }
+ .search-filter.filled .input-group-addon select {
+ border-right: 0px;
+ }
+ .search-filter.filled {
+ .ms-choice {
box-shadow: var(--slick-filled-filter-box-shadow, $slick-filled-filter-box-shadow);
- }
- .search-filter.filled .input-group-addon select {
- border-right: 0px;
- }
- .search-filter.filled {
- .ms-choice {
- box-shadow: var(--slick-filled-filter-box-shadow, $slick-filled-filter-box-shadow);
- border: var(--slick-filled-filter-border, $slick-filled-filter-border);
- span {
- font-weight: var(--slick-filled-filter-font-weight, $slick-filled-filter-font-weight);
- color: var(--slick-filled-filter-color, $slick-filled-filter-color);
- }
+ border: var(--slick-filled-filter-border, $slick-filled-filter-border);
+ span {
+ font-weight: var(--slick-filled-filter-font-weight, $slick-filled-filter-font-weight);
+ color: var(--slick-filled-filter-color, $slick-filled-filter-color);
}
}
+ }
}
\ No newline at end of file
diff --git a/packages/common/src/styles/slick-plugins.scss b/packages/common/src/styles/slick-plugins.scss
index b60da4ce4..bc07acfe1 100644
--- a/packages/common/src/styles/slick-plugins.scss
+++ b/packages/common/src/styles/slick-plugins.scss
@@ -965,47 +965,51 @@ input.flatpickr.form-control {
// ----------------------------------------------
// Input Slider Filter (with vanilla html)
// ----------------------------------------------
-.ui-widget.ui-widget-content {
- border: 0;
+.slider-single {
+ input[type="range"] {
+ &::-webkit-slider-runnable-track, &:focus::-webkit-slider-runnable-track {
+ background: var(--slick-slider-filter-bgcolor, $slick-slider-filter-bgcolor);
+ }
+ &::-moz-range-track, &:focus::-moz-range-track {
+ background: var(--slick-slider-filter-bgcolor, $slick-slider-filter-bgcolor);
+ }
+ &::-ms-fill-lower {
+ background: var(--slick-slider-filter-fill-lower-color, $slick-slider-filter-fill-lower-color);
+ &:focus {
+ background: var(--slick-slider-filter-fill-focus-lower-color, $slick-slider-filter-fill-focus-lower-color);
+ }
+ }
+ &::-ms-fill-upper, &:focus::-ms-fill-upper {
+ background: var(--slick-slider-filter-bgcolor, $slick-slider-filter-bgcolor);
+ }
+
+ &::-webkit-slider-runnable-track, &:focus::-webkit-slider-runnable-track {
+ background: var(--slick-slider-filter-bgcolor, $slick-slider-filter-bgcolor);
+ }
+ }
}
input.slider-editor-input[type=range],
input.slider-filter-input[type=range] {
/*removes default webkit styles*/
- -webkit-appearance: none;
- height: var(--slick-slider-filter-height, $slick-slider-filter-height);
+ appearance: none;
flex: 1;
-
+ height: var(--slick-slider-filter-height, $slick-slider-filter-height);
padding: var(--slick-slider-filter-runnable-track-padding, $slick-slider-filter-runnable-track-padding);
/* change runnable track color while in focus on all browsers */
&:focus {
outline: none;
-
- &::-webkit-slider-runnable-track {
- background: var(--slick-slider-filter-runnable-track-bgcolor, $slick-slider-filter-runnable-track-bgcolor);
- }
- &::-moz-range-track {
- background: var(--slick-slider-filter-runnable-track-bgcolor, $slick-slider-filter-runnable-track-bgcolor);
- }
- &::-ms-fill-lower {
- background: var(--slick-slider-filter-fill-focus-lower-color, $slick-slider-filter-fill-focus-lower-color);
- }
- &::-ms-fill-upper {
- background: var(--slick-slider-filter-runnable-track-bgcolor, $slick-slider-filter-runnable-track-bgcolor);
- }
}
/* WebKit specific (Opera/Chrome/Safari) */
&::-webkit-slider-runnable-track {
height: var(--slick-slider-filter-runnable-track-height, $slick-slider-filter-runnable-track-height);
- background: var(--slick-slider-filter-bgcolor, $slick-slider-filter-bgcolor);
border: none;
border-radius: 3px;
}
&::-webkit-slider-thumb {
cursor: var(--slick-slider-filter-thumb-cursor, $slick-slider-filter-thumb-cursor);
-webkit-appearance: none;
- border: none;
height: var(--slick-slider-filter-thumb-size, $slick-slider-filter-thumb-size);
width: var(--slick-slider-filter-thumb-size, $slick-slider-filter-thumb-size);
border-radius: var(--slick-slider-filter-thumb-border-radius, $slick-slider-filter-thumb-border-radius);
@@ -1021,12 +1025,10 @@ input.slider-filter-input[type=range] {
&::-moz-range-track {
height: var(--slick-slider-filter-runnable-track-height, $slick-slider-filter-runnable-track-height);
- background: var(--slick-slider-filter-bgcolor, $slick-slider-filter-bgcolor);
border: none;
border-radius: 3px;
}
&::-moz-range-thumb {
- border: none;
cursor: var(--slick-slider-filter-thumb-cursor, $slick-slider-filter-thumb-cursor);
height: var(--slick-slider-filter-thumb-height, $slick-slider-filter-thumb-height);
width: var(--slick-slider-filter-thumb-width, $slick-slider-filter-thumb-width);
@@ -1064,7 +1066,6 @@ input.slider-filter-input[type=range] {
border-radius: 10px;
}
&::-ms-thumb {
- border: none;
cursor: var(--slick-slider-filter-thumb-cursor, $slick-slider-filter-thumb-cursor);
height: var(--slick-slider-filter-thumb-height, $slick-slider-filter-thumb-height);
width: var(--slick-slider-filter-thumb-width, $slick-slider-filter-thumb-width);
@@ -1076,6 +1077,14 @@ input.slider-filter-input[type=range] {
&::-ms-tooltip {
display: none;
}
+ &:active::-webkit-slider-thumb {
+ background-color: var(--slick-slider-filter-thumb-active-bg-color, $slick-slider-filter-thumb-active-bg-color);
+ border: 2px solid var(--slick-slider-filter-thumb-color, $slick-slider-filter-thumb-color);
+ }
+ &:active::-moz-range-thumb {
+ background-color: var(--slick-slider-filter-thumb-active-bg-color, $slick-slider-filter-thumb-active-bg-color);
+ border: 2px solid var(--slick-slider-filter-thumb-color, $slick-slider-filter-thumb-color);
+ }
}
.search-filter {
height: var(--slick-header-input-height, $slick-header-input-height);
@@ -1117,67 +1126,81 @@ input.slider-editor-input[type=range] {
}
}
+
// ----------------------------------------------
-// Input Slider Range Filter (using jQuery UI)
+// Input Slider Range Filter (with vanilla html)
// ----------------------------------------------
-.slider-range-container {
- height: var(--slick-slider-range-filter-height, $slick-slider-range-filter-height);
- padding: var(--slick-slider-range-filter-padding, $slick-slider-range-filter-padding);
- .ui-slider {
- position: relative;
+.slider-container {
+ .slider-range-value {
+ padding: 0;
+ height: 100%;
+ .input-group-text {
+ padding: var(--slick-slider-filter-number-padding, $slick-slider-filter-number-padding);
+ font-size: var(--slick-slider-filter-number-font-size, $slick-slider-filter-number-font-size);
+ }
+ }
+ &:not(.input-group) {
+ .slider-filter-input {
+ border-radius: var(--slick-slider-range-filter-border-radius, $slick-slider-range-filter-border-radius);
+ }
+ }
+}
+.slider-range-container {
+ position: relative;
+ width: 100%;
- .ui-slider-handle {
- position: absolute;
- top: var(--slick-slider-range-filter-thumb-top, $slick-slider-range-filter-thumb-top);
- border-radius: var(--slick-slider-range-filter-thumb-border-radius, $slick-slider-range-filter-thumb-border-radius);
- cursor: var(--slick-slider-range-filter-thumb-cursor, $slick-slider-range-filter-thumb-cursor);
- border: var(--slick-slider-range-filter-thumb-border, $slick-slider-range-filter-thumb-border);
- height: var(--slick-slider-range-filter-thumb-size, $slick-slider-range-filter-thumb-size);
- width: var(--slick-slider-range-filter-thumb-size, $slick-slider-range-filter-thumb-size);
- background-color: var(--slick-slider-range-filter-thumb-color, $slick-slider-range-filter-thumb-color);
+ .slider-track {
+ cursor: var(--slick-slider-filter-runnable-track-cursor, $slick-slider-filter-runnable-track-cursor);
+ width: calc(100% - 16px);
+ height: var(--slick-slider-filter-runnable-track-height, $slick-slider-filter-runnable-track-height);
+ position: absolute;
+ margin: auto;
+ margin-left: 8px;
+ top: 0;
+ bottom: 0;
+ border-radius: 3px;
+ background: var(--slick-slider-filter-runnable-track-bgcolor, $slick-slider-filter-runnable-track-bgcolor);
+ }
- &:focus {
- outline: none;
- }
+ input[type="range"] {
+ position: absolute;
+ background-color: transparent;
+ pointer-events: none;
+ width: 100%;
+ &.focus {
+ outline: 0;
+ border-color: var(--slick-slider-range-focus-border-color, $slick-slider-range-focus-border-color);
+ box-shadow: var(--slick-slider-range-focus-box-shadow, $slick-slider-range-focus-box-shadow);
}
}
- .ui-slider-horizontal {
- top: var(--slick-slider-range-filter-runnable-track-top, $slick-slider-range-filter-runnable-track-top);
- height: var(--slick-slider-range-filter-runnable-track-height, $slick-slider-range-filter-runnable-track-height);
- background-color: var(--slick-slider-range-filter-bgcolor, $slick-slider-range-filter-bgcolor);
+ input[type="range"]::-webkit-slider-runnable-track {
+ -webkit-appearance: none;
}
- .input-group-text {
- border: 0;
-
+ input[type="range"]::-moz-range-track {
+ -moz-appearance: none;
}
-}
-.slider-range-container.slider-values {
- padding: 0;
- .ui-slider-horizontal {
- flex: 1;
- width: var(--slick-slider-range-filter-values-slider-width, $slick-slider-range-filter-values-slider-width);
- top: var(--slick-slider-range-filter-values-slider-top, $slick-slider-range-filter-values-slider-top);
- margin: var(--slick-slider-range-filter-values-slider-margin, $slick-slider-range-filter-values-slider-margin);
+ input[type="range"]::-ms-track {
+ appearance: none;
}
- .slider-range-value {
- padding: 0;
- border: 0;
- height: 100%;
- .input-group-text {
- padding: var(--slick-slider-filter-number-padding, $slick-slider-filter-number-padding);
- font-size: var(--slick-slider-filter-number-font-size, $slick-slider-filter-number-font-size);
- }
+ input[type="range"]::-webkit-slider-thumb {
+ pointer-events: auto;
}
- .input-group-prepend.slider-range-value {
- border-right: var(--slick-slider-range-filter-border, $slick-slider-range-filter-border);
+ input[type="range"]::-moz-range-thumb {
+ pointer-events: auto;
}
- .input-group-append.slider-range-value {
- border-left: var(--slick-slider-range-filter-border, $slick-slider-range-filter-border);
+ input[type="range"]::-ms-thumb {
+ appearance: none;
+ pointer-events: auto;
}
}
+.slider-range-container.slider-values {
+ display: flex;
+ padding: 0;
+}
+
// ---------------------------------------------------------
// Row Detail View Plugin
// ---------------------------------------------------------
diff --git a/packages/common/src/styles/slick-without-bootstrap-min-styling.scss b/packages/common/src/styles/slick-without-bootstrap-min-styling.scss
index 61c7244cd..a0262adf5 100644
--- a/packages/common/src/styles/slick-without-bootstrap-min-styling.scss
+++ b/packages/common/src/styles/slick-without-bootstrap-min-styling.scss
@@ -94,6 +94,9 @@ $slick-form-control-focus-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px
.input-group .form-control, .input-group-addon, .input-group-btn {
display: table-cell;
}
+ .input-group-addon:first-child {
+ border-right: 0;
+ }
.input-group-addon:last-child {
border-left: 0;
}
diff --git a/test/cypress/e2e/example14.cy.js b/test/cypress/e2e/example14.cy.js
index c2980b75b..3ca2cfbba 100644
--- a/test/cypress/e2e/example14.cy.js
+++ b/test/cypress/e2e/example14.cy.js
@@ -19,7 +19,7 @@ describe('Example 14 - Columns Resize by Content', { retries: 1 }, () => {
cy.get('.slick-row').find('.slick-cell:nth(1)').invoke('width').should('equal', 83);
cy.get('.slick-row').find('.slick-cell:nth(2)').invoke('width').should('equal', 98);
cy.get('.slick-row').find('.slick-cell:nth(3)').invoke('width').should('equal', 67);
- cy.get('.slick-row').find('.slick-cell:nth(4)').invoke('width').should('equal', 110);
+ cy.get('.slick-row').find('.slick-cell:nth(4)').invoke('width').should('equal', 160);
cy.get('.slick-row').find('.slick-cell:nth(5)').invoke('width').should('equal', 106);
cy.get('.slick-row').find('.slick-cell:nth(6)').invoke('width').should('equal', 88);
cy.get('.slick-row').find('.slick-cell:nth(7)').invoke('width').should('equal', 68);
@@ -35,7 +35,7 @@ describe('Example 14 - Columns Resize by Content', { retries: 1 }, () => {
cy.get('.slick-row').find('.slick-cell:nth(1)').invoke('width').should('equal', 75);
cy.get('.slick-row').find('.slick-cell:nth(2)').invoke('width').should('equal', 98);
cy.get('.slick-row').find('.slick-cell:nth(3)').invoke('width').should('equal', 67);
- cy.get('.slick-row').find('.slick-cell:nth(4)').invoke('width').should('equal', 102);
+ cy.get('.slick-row').find('.slick-cell:nth(4)').invoke('width').should('equal', 152);
cy.get('.slick-row').find('.slick-cell:nth(5)').invoke('width').should('equal', 98);
cy.get('.slick-row').find('.slick-cell:nth(6)').invoke('width').should('equal', 80);
cy.get('.slick-row').find('.slick-cell:nth(7)').invoke('width').should('equal', 68);
@@ -51,7 +51,7 @@ describe('Example 14 - Columns Resize by Content', { retries: 1 }, () => {
cy.get('.slick-row').find('.slick-cell:nth(1)').invoke('width').should('be.lt', 75);
cy.get('.slick-row').find('.slick-cell:nth(2)').invoke('width').should('be.lt', 95);
cy.get('.slick-row').find('.slick-cell:nth(3)').invoke('width').should('be.lt', 70);
- cy.get('.slick-row').find('.slick-cell:nth(4)').invoke('width').should('be.lt', 100);
+ cy.get('.slick-row').find('.slick-cell:nth(4)').invoke('width').should('be.lt', 150);
cy.get('.slick-row').find('.slick-cell:nth(5)').invoke('width').should('be.lt', 100);
cy.get('.slick-row').find('.slick-cell:nth(6)').invoke('width').should('be.lt', 85);
cy.get('.slick-row').find('.slick-cell:nth(7)').invoke('width').should('be.lt', 70);