From 145dd0cf4848580d47b356f000169527dbb528aa Mon Sep 17 00:00:00 2001 From: lgersen Date: Tue, 14 May 2024 11:33:47 +0200 Subject: [PATCH 1/9] Update nouislider.d.ts --- dist/nouislider.d.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dist/nouislider.d.ts b/dist/nouislider.d.ts index 118ad81..c2e6eff 100644 --- a/dist/nouislider.d.ts +++ b/dist/nouislider.d.ts @@ -91,7 +91,8 @@ interface RangePips extends BasePips { mode: PipsMode.Range; } declare type Pips = PositionsPips | ValuesPips | CountPips | StepsPips | RangePips; -declare type StartValues = string | number | (string | number)[]; +declare type ValueArgument = number | string | null; +declare type StartValues = ValueArgument | ValueArgument[]; declare type HandleAttributes = { [key: string]: string; }; @@ -131,8 +132,8 @@ export interface API { on: (eventName: string, callback: EventCallback) => void; off: (eventName: string) => void; get: (unencoded?: boolean) => GetResult; - set: (input: number | string | (number | string)[], fireSetEvent?: boolean, exactInput?: boolean) => void; - setHandle: (handleNumber: number, value: number | string, fireSetEvent?: boolean, exactInput?: boolean) => void; + set: (input: ValueArgument | ValueArgument[], fireSetEvent?: boolean, exactInput?: boolean) => void; + setHandle: (handleNumber: number, value: ValueArgument, fireSetEvent?: boolean, exactInput?: boolean) => void; reset: (fireSetEvent?: boolean) => void; disable: (handleNumber?: number) => void; enable: (handleNumber?: number) => void; @@ -141,6 +142,7 @@ export interface API { target: HTMLElement; removePips: () => void; removeTooltips: () => void; + getPositions: () => number[]; getTooltips: () => { [handleNumber: number]: HTMLElement | false; }; From ae93f93ba46f7df72a8168e6ee851c04fe742e49 Mon Sep 17 00:00:00 2001 From: lgersen Date: Tue, 14 May 2024 11:33:52 +0200 Subject: [PATCH 2/9] Create RELEASE.md --- RELEASE.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..a0bcd9f --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,6 @@ +- `npm login` +- Update version in `package.json` +- Run `npm audit fix` +- Update `changelog.md` +- Run `npm publish -otp=<2FA VALUE FROM AUTHENTICATOR>` +- Create a new release on https://github.com/leongersen/noUiSlider/releases From 4697c1829acaf182a2b6536299dc271de2bc6529 Mon Sep 17 00:00:00 2001 From: 5andr0 Date: Mon, 10 Jun 2024 19:42:42 +0300 Subject: [PATCH 3/9] Inverted Connects for unconstrained behavior This implementation allows connects to invert when handles pass each other in unconstrained behavior. Added possibility to manually update connects via updateOptions without having to destroy and recreate the slider (which would lose the drag). Also added invertConnects to the API for more control. Set behavior to "unconstrained-invert-connects" to enable this feature. --- documentation/behaviour-option.php | 30 ++++++++ .../behaviour-option/invert-connects-link.js | 9 +++ .../behaviour-option/invert-connects.js | 12 ++++ src/nouislider.ts | 72 +++++++++++++++++-- 4 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 documentation/behaviour-option/invert-connects-link.js create mode 100644 documentation/behaviour-option/invert-connects.js diff --git a/documentation/behaviour-option.php b/documentation/behaviour-option.php index c892b1d..c9df959 100644 --- a/documentation/behaviour-option.php +++ b/documentation/behaviour-option.php @@ -36,6 +36,7 @@
  • Hover
  • Unconstrained
  • Smooth steps
  • +
  • Invert connects
  • @@ -262,3 +263,32 @@ + + + +

    Invert Connects

    + +
    + +
    +

    With this option set, connects invert when handles pass each other.

    + +

    Requires the unconstrained behaviour and the connect option.

    +
    +
    + + + +
    +
    + +
    + + +
    Show the slider value
    + +
    + +
    +
    +
    diff --git a/documentation/behaviour-option/invert-connects-link.js b/documentation/behaviour-option/invert-connects-link.js new file mode 100644 index 0000000..26cd2dd --- /dev/null +++ b/documentation/behaviour-option/invert-connects-link.js @@ -0,0 +1,9 @@ +var invertConnectsValues = document.getElementById('invert-connects-values'); + +invertConnectsSlider.noUiSlider.on('update', function (values) { + var minToHHMM = function(mins) { + var pad = function(n) { return ('0'+n).slice(-2); }; + return [mins / 60 ^ 0, mins % 60].map(pad).join(':'); + }; + invertConnectsValues.innerHTML = values.map(minToHHMM).join(' - '); +}); \ No newline at end of file diff --git a/documentation/behaviour-option/invert-connects.js b/documentation/behaviour-option/invert-connects.js new file mode 100644 index 0000000..236687b --- /dev/null +++ b/documentation/behaviour-option/invert-connects.js @@ -0,0 +1,12 @@ +var invertConnectsSlider = document.getElementById('invert-connects'); + +noUiSlider.create(invertConnectsSlider, { + start: [20*60, 8*60], + behaviour: 'unconstrained-invert-connects', + step: 15, + connect: true, + range: { + 'min': 0, + 'max': 24*60 + } +}); \ No newline at end of file diff --git a/src/nouislider.ts b/src/nouislider.ts index a59b868..5978522 100644 --- a/src/nouislider.ts +++ b/src/nouislider.ts @@ -131,11 +131,11 @@ interface UpdatableOptions { format?: Formatter; tooltips?: boolean | PartialFormatter | (boolean | PartialFormatter)[]; animate?: boolean; + connect?: "lower" | "upper" | boolean | boolean[]; } export interface Options extends UpdatableOptions { range: Range; - connect?: "lower" | "upper" | boolean | boolean[]; orientation?: "vertical" | "horizontal"; direction?: "ltr" | "rtl"; behaviour?: string; @@ -160,6 +160,7 @@ interface Behaviour { snap: boolean; hover: boolean; unconstrained: boolean; + invertConnects: boolean; } interface ParsedOptions { @@ -1136,6 +1137,7 @@ function testBehaviour(parsed: ParsedOptions, entry: unknown): void { const snap = entry.indexOf("snap") >= 0; const hover = entry.indexOf("hover") >= 0; const unconstrained = entry.indexOf("unconstrained") >= 0; + const invertConnects = entry.indexOf("invert-connects") >= 0; const dragAll = entry.indexOf("drag-all") >= 0; const smoothSteps = entry.indexOf("smooth-steps") >= 0; @@ -1161,6 +1163,7 @@ function testBehaviour(parsed: ParsedOptions, entry: unknown): void { snap: snap, hover: hover, unconstrained: unconstrained, + invertConnects: invertConnects, }; } @@ -1367,6 +1370,7 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O // Slider DOM Nodes const scope_Target = target; let scope_Base: HTMLElement; + let scope_ConnectBase: HTMLElement; let scope_Handles: Origin[]; let scope_Connects: (HTMLElement | false)[]; let scope_Pips: HTMLElement | null; @@ -1379,6 +1383,7 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O const scope_HandleNumbers: number[] = []; let scope_ActiveHandlesCount = 0; const scope_Events: { [key: string]: EventCallback[] } = {}; + let scope_ConnectsInverted: boolean = false; // Document Nodes const scope_Document = target.ownerDocument; @@ -1452,12 +1457,12 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O // Add handles to the slider base. function addElements(connectOptions: boolean[], base: HTMLElement): void { - const connectBase = addNodeTo(base, options.cssClasses.connects); + scope_ConnectBase = addNodeTo(base, options.cssClasses.connects); scope_Handles = []; scope_Connects = []; - scope_Connects.push(addConnect(connectBase, connectOptions[0])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[0])); // [::::O====O====O====] // connectOptions = [0, 1, 1, 1] @@ -1466,7 +1471,7 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O // Keep a list of all added handles. scope_Handles.push(addOrigin(base, i)); scope_HandleNumbers[i] = i; - scope_Connects.push(addConnect(connectBase, connectOptions[i + 1])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[i + 1])); } } @@ -2666,8 +2671,27 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O (scope_Handles[handleNumber].style as CSSStyleDeclarationIE10)[options.transformRule] = translateRule; + if( + options.events.invertConnects && + // sanity check for at least 2 handles + scope_Locations.length > 1 && + // check if handles passed each other, but don't match the ConnectsInverted state + scope_ConnectsInverted !== !scope_Locations.every( + (position: number, index: number, locations: number[]): boolean => + index === 0 || position >= locations[index - 1] + )) { + // when invertConnects is set, automatically invert connects when handles pass each other + invertConnects(); + } + updateConnect(handleNumber); updateConnect(handleNumber + 1); + + if(scope_ConnectsInverted) { + // When connects are inverted, we also have to update adjacent connects + updateConnect(handleNumber - 1); + updateConnect(handleNumber + 2); + } } // Handles before the slider middle are stacked later = higher, @@ -2719,15 +2743,23 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O return; } + // Create a copy of locations, so we can sort them for the local scope logic + let locations = scope_Locations.slice(); + if (scope_ConnectsInverted) { + locations.sort(function(a, b) { + return a - b; + }); + } + let l = 0; let h = 100; if (index !== 0) { - l = scope_Locations[index - 1]; + l = locations[index - 1]; } if (index !== scope_Connects.length - 1) { - h = scope_Locations[index]; + h = locations[index]; } // We use two rules: @@ -2968,6 +3000,7 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O "format", "pips", "tooltips", + "connect", ]; // Only change options that we're actually passed to update. @@ -3012,6 +3045,33 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O scope_Locations = []; valueSet(isSet(optionsToUpdate.start) ? optionsToUpdate.start : v, fireSetEvent); + + // Update connects only if it was set + if (optionsToUpdate.connect) { + // IE supported way of removing children including event handlers + while (scope_ConnectBase.firstChild) { + scope_ConnectBase.removeChild(scope_ConnectBase.firstChild); + } + + // Adding new connects according to the new connect options + for (let i = 0; i <= options.handles; i++) { + scope_Connects[i] = addConnect(scope_ConnectBase, options.connect[i]); + updateConnect(i); + } + + // readding drag events for the new connect elements + // to ignore the other events we have to negate the 'if (!behaviour.fixed)' check + bindSliderEvents({ drag: options.events.drag, fixed: true } as Behaviour); + } + } + + // Invert options for connect handles + function invertConnects() { + scope_ConnectsInverted = !scope_ConnectsInverted; + updateOptions({ + // inverse the connect boolean array + connect: options.connect.map((b: boolean) => !b) + }, false); // don't fire the set event } // Initialization steps From 3406bc1075f969626ce6e99d6ad381054b999a52 Mon Sep 17 00:00:00 2001 From: lgersen Date: Mon, 10 Jun 2024 20:55:12 +0200 Subject: [PATCH 4/9] Bypass full updateOptions, fix eslint, check for two handles --- documentation/behaviour-option.php | 6 +- .../behaviour-option/invert-connects-link.js | 2 +- .../behaviour-option/invert-connects.js | 2 +- documentation/more.php | 2 +- src/nouislider.ts | 71 +++++++++++-------- 5 files changed, 49 insertions(+), 34 deletions(-) diff --git a/documentation/behaviour-option.php b/documentation/behaviour-option.php index c9df959..e242046 100644 --- a/documentation/behaviour-option.php +++ b/documentation/behaviour-option.php @@ -10,9 +10,9 @@
    -

    noUiSlider offers several ways to handle user interaction. The range can be made draggable, or handles can move to tapped positions. All these effects are optional, and can be enable by adding their keyword to the behaviour option.

    +

    noUiSlider offers several ways to handle user interaction. The range can be made draggable, or handles can move to tapped positions. All these effects are optional, and can be enabled by adding their keyword to the behaviour option.

    -

    This option accepts a "-" separated list of "drag", "drag-all", "tap", "fixed", "snap", "unconstrained" or "none".

    +

    This option accepts a "-" separated list of "drag", "drag-all", "tap", "fixed", "snap", "unconstrained", "invert-connects" or "none".

    @@ -273,7 +273,7 @@

    With this option set, connects invert when handles pass each other.

    -

    Requires the unconstrained behaviour and the connect option.

    +

    Requires the unconstrained behaviour and the connect option. This option is only applicable for sliders with two handles.

    diff --git a/documentation/behaviour-option/invert-connects-link.js b/documentation/behaviour-option/invert-connects-link.js index 26cd2dd..f9453f7 100644 --- a/documentation/behaviour-option/invert-connects-link.js +++ b/documentation/behaviour-option/invert-connects-link.js @@ -6,4 +6,4 @@ invertConnectsSlider.noUiSlider.on('update', function (values) { return [mins / 60 ^ 0, mins % 60].map(pad).join(':'); }; invertConnectsValues.innerHTML = values.map(minToHHMM).join(' - '); -}); \ No newline at end of file +}); diff --git a/documentation/behaviour-option/invert-connects.js b/documentation/behaviour-option/invert-connects.js index 236687b..52d2a2a 100644 --- a/documentation/behaviour-option/invert-connects.js +++ b/documentation/behaviour-option/invert-connects.js @@ -9,4 +9,4 @@ noUiSlider.create(invertConnectsSlider, { 'min': 0, 'max': 24*60 } -}); \ No newline at end of file +}); diff --git a/documentation/more.php b/documentation/more.php index 1cea5eb..b15d106 100644 --- a/documentation/more.php +++ b/documentation/more.php @@ -75,7 +75,7 @@
    -

    noUiSlider has an update method that can change the 'margin', 'padding', 'limit', 'step', 'range', 'pips', 'tooltips', 'animate' and 'snap' options.

    +

    noUiSlider has an update method that can change the 'margin', 'padding', 'limit', 'step', 'range', 'pips', 'tooltips', 'connect', 'animate' and 'snap' options.

    All other options require changes to the slider's HTML or event bindings.

    diff --git a/src/nouislider.ts b/src/nouislider.ts index 5978522..e7ee831 100644 --- a/src/nouislider.ts +++ b/src/nouislider.ts @@ -1150,6 +1150,10 @@ function testBehaviour(parsed: ParsedOptions, entry: unknown): void { testMargin(parsed, parsed.start[1] - parsed.start[0]); } + if (invertConnects && parsed.handles !== 2) { + throw new Error("noUiSlider: 'invert-connects' behaviour must be used with 2 handles"); + } + if (unconstrained && (parsed.margin || parsed.limit)) { throw new Error("noUiSlider: 'unconstrained' behaviour cannot be used with margin or limit"); } @@ -1383,7 +1387,7 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O const scope_HandleNumbers: number[] = []; let scope_ActiveHandlesCount = 0; const scope_Events: { [key: string]: EventCallback[] } = {}; - let scope_ConnectsInverted: boolean = false; + let scope_ConnectsInverted = false; // Document Nodes const scope_Document = target.ownerDocument; @@ -2671,23 +2675,27 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O (scope_Handles[handleNumber].style as CSSStyleDeclarationIE10)[options.transformRule] = translateRule; - if( - options.events.invertConnects && - // sanity check for at least 2 handles - scope_Locations.length > 1 && + // sanity check for at least 2 handles (e.g. during setup) + if (options.events.invertConnects && scope_Locations.length > 1) { // check if handles passed each other, but don't match the ConnectsInverted state - scope_ConnectsInverted !== !scope_Locations.every( - (position: number, index: number, locations: number[]): boolean => + const handlesAreInOrder = scope_Locations.every( + (position: number, index: number, locations: number[]): boolean => index === 0 || position >= locations[index - 1] - )) { - // when invertConnects is set, automatically invert connects when handles pass each other + ); + + if (scope_ConnectsInverted !== !handlesAreInOrder) { + // invert connects when handles pass each other invertConnects(); + + // invertConnects already updates all connect elements + return; } + } updateConnect(handleNumber); updateConnect(handleNumber + 1); - - if(scope_ConnectsInverted) { + + if (scope_ConnectsInverted) { // When connects are inverted, we also have to update adjacent connects updateConnect(handleNumber - 1); updateConnect(handleNumber + 2); @@ -2744,9 +2752,10 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O } // Create a copy of locations, so we can sort them for the local scope logic - let locations = scope_Locations.slice(); + const locations = scope_Locations.slice(); + if (scope_ConnectsInverted) { - locations.sort(function(a, b) { + locations.sort(function (a, b) { return a - b; }); } @@ -3048,30 +3057,36 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O // Update connects only if it was set if (optionsToUpdate.connect) { - // IE supported way of removing children including event handlers - while (scope_ConnectBase.firstChild) { - scope_ConnectBase.removeChild(scope_ConnectBase.firstChild); - } + updateConnectOption(); + } + } - // Adding new connects according to the new connect options - for (let i = 0; i <= options.handles; i++) { - scope_Connects[i] = addConnect(scope_ConnectBase, options.connect[i]); - updateConnect(i); - } + function updateConnectOption() { + // IE supported way of removing children including event handlers + while (scope_ConnectBase.firstChild) { + scope_ConnectBase.removeChild(scope_ConnectBase.firstChild); + } - // readding drag events for the new connect elements - // to ignore the other events we have to negate the 'if (!behaviour.fixed)' check - bindSliderEvents({ drag: options.events.drag, fixed: true } as Behaviour); + // Adding new connects according to the new connect options + for (let i = 0; i <= options.handles; i++) { + scope_Connects[i] = addConnect(scope_ConnectBase, options.connect[i]); + updateConnect(i); } + + // re-adding drag events for the new connect elements + // to ignore the other events we have to negate the 'if (!behaviour.fixed)' check + bindSliderEvents({ drag: options.events.drag, fixed: true } as Behaviour); } // Invert options for connect handles function invertConnects() { scope_ConnectsInverted = !scope_ConnectsInverted; - updateOptions({ + testConnect( + options, // inverse the connect boolean array - connect: options.connect.map((b: boolean) => !b) - }, false); // don't fire the set event + options.connect.map((b: boolean) => !b) + ); + updateConnectOption(); } // Initialization steps From 787ad0c56549b06467190b06c6f4c3db76359049 Mon Sep 17 00:00:00 2001 From: lgersen Date: Mon, 10 Jun 2024 21:02:58 +0200 Subject: [PATCH 5/9] noUiSlider 15.8.0 --- CHANGELOG.MD | 4 +++ RELEASE.md | 1 + dist/nouislider.d.ts | 2 +- dist/nouislider.js | 69 ++++++++++++++++++++++++++++++++++++++--- dist/nouislider.min.js | 2 +- dist/nouislider.min.mjs | 2 +- dist/nouislider.mjs | 69 ++++++++++++++++++++++++++++++++++++++--- package-lock.json | 4 +-- package.json | 2 +- 9 files changed, 139 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 2c862e8..de62755 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,5 +1,9 @@ # Changelog +### 15.8.0 (*2024-06-10*) +- Added: Allow `connect` option to get updated (#1272); +- Added: `invert-connects` behaviour (#1262, #1272); + ### 15.7.2 (*2024-05-14*) - Added: `getPositions` to Typescript definitions (#1270); - Added: Allow `null` in `set` Typescript definitions (#1271); diff --git a/RELEASE.md b/RELEASE.md index a0bcd9f..a44973e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,4 +3,5 @@ - Run `npm audit fix` - Update `changelog.md` - Run `npm publish -otp=<2FA VALUE FROM AUTHENTICATOR>` +- Commit changes to changelog and build files - Create a new release on https://github.com/leongersen/noUiSlider/releases diff --git a/dist/nouislider.d.ts b/dist/nouislider.d.ts index c2e6eff..9cb9e12 100644 --- a/dist/nouislider.d.ts +++ b/dist/nouislider.d.ts @@ -108,10 +108,10 @@ interface UpdatableOptions { format?: Formatter; tooltips?: boolean | PartialFormatter | (boolean | PartialFormatter)[]; animate?: boolean; + connect?: "lower" | "upper" | boolean | boolean[]; } export interface Options extends UpdatableOptions { range: Range; - connect?: "lower" | "upper" | boolean | boolean[]; orientation?: "vertical" | "horizontal"; direction?: "ltr" | "rtl"; behaviour?: string; diff --git a/dist/nouislider.js b/dist/nouislider.js index 1dff19b..2d3e054 100644 --- a/dist/nouislider.js +++ b/dist/nouislider.js @@ -713,6 +713,7 @@ var snap = entry.indexOf("snap") >= 0; var hover = entry.indexOf("hover") >= 0; var unconstrained = entry.indexOf("unconstrained") >= 0; + var invertConnects = entry.indexOf("invert-connects") >= 0; var dragAll = entry.indexOf("drag-all") >= 0; var smoothSteps = entry.indexOf("smooth-steps") >= 0; if (fixed) { @@ -722,6 +723,9 @@ // Use margin to enforce fixed state testMargin(parsed, parsed.start[1] - parsed.start[0]); } + if (invertConnects && parsed.handles !== 2) { + throw new Error("noUiSlider: 'invert-connects' behaviour must be used with 2 handles"); + } if (unconstrained && (parsed.margin || parsed.limit)) { throw new Error("noUiSlider: 'unconstrained' behaviour cannot be used with margin or limit"); } @@ -734,6 +738,7 @@ snap: snap, hover: hover, unconstrained: unconstrained, + invertConnects: invertConnects, }; } function testTooltips(parsed, entry) { @@ -904,6 +909,7 @@ // Slider DOM Nodes var scope_Target = target; var scope_Base; + var scope_ConnectBase; var scope_Handles; var scope_Connects; var scope_Pips; @@ -915,6 +921,7 @@ var scope_HandleNumbers = []; var scope_ActiveHandlesCount = 0; var scope_Events = {}; + var scope_ConnectsInverted = false; // Document Nodes var scope_Document = target.ownerDocument; var scope_DocumentElement = options.documentElement || scope_Document.documentElement; @@ -971,17 +978,17 @@ } // Add handles to the slider base. function addElements(connectOptions, base) { - var connectBase = addNodeTo(base, options.cssClasses.connects); + scope_ConnectBase = addNodeTo(base, options.cssClasses.connects); scope_Handles = []; scope_Connects = []; - scope_Connects.push(addConnect(connectBase, connectOptions[0])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[0])); // [::::O====O====O====] // connectOptions = [0, 1, 1, 1] for (var i = 0; i < options.handles; i++) { // Keep a list of all added handles. scope_Handles.push(addOrigin(base, i)); scope_HandleNumbers[i] = i; - scope_Connects.push(addConnect(connectBase, connectOptions[i + 1])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[i + 1])); } } // Initialize a single slider. @@ -1930,8 +1937,26 @@ var translation = transformDirection(to, 0) - scope_DirOffset; var translateRule = "translate(" + inRuleOrder(translation + "%", "0") + ")"; scope_Handles[handleNumber].style[options.transformRule] = translateRule; + // sanity check for at least 2 handles (e.g. during setup) + if (options.events.invertConnects && scope_Locations.length > 1) { + // check if handles passed each other, but don't match the ConnectsInverted state + var handlesAreInOrder = scope_Locations.every(function (position, index, locations) { + return index === 0 || position >= locations[index - 1]; + }); + if (scope_ConnectsInverted !== !handlesAreInOrder) { + // invert connects when handles pass each other + invertConnects(); + // invertConnects already updates all connect elements + return; + } + } updateConnect(handleNumber); updateConnect(handleNumber + 1); + if (scope_ConnectsInverted) { + // When connects are inverted, we also have to update adjacent connects + updateConnect(handleNumber - 1); + updateConnect(handleNumber + 2); + } } // Handles before the slider middle are stacked later = higher, // Handles after the middle later is lower @@ -1961,13 +1986,20 @@ if (!scope_Connects[index]) { return; } + // Create a copy of locations, so we can sort them for the local scope logic + var locations = scope_Locations.slice(); + if (scope_ConnectsInverted) { + locations.sort(function (a, b) { + return a - b; + }); + } var l = 0; var h = 100; if (index !== 0) { - l = scope_Locations[index - 1]; + l = locations[index - 1]; } if (index !== scope_Connects.length - 1) { - h = scope_Locations[index]; + h = locations[index]; } // We use two rules: // 'translate' to change the left/top offset; @@ -2159,6 +2191,7 @@ "format", "pips", "tooltips", + "connect", ]; // Only change options that we're actually passed to update. updateAble.forEach(function (name) { @@ -2196,6 +2229,32 @@ // Invalidate the current positioning so valueSet forces an update. scope_Locations = []; valueSet(isSet(optionsToUpdate.start) ? optionsToUpdate.start : v, fireSetEvent); + // Update connects only if it was set + if (optionsToUpdate.connect) { + updateConnectOption(); + } + } + function updateConnectOption() { + // IE supported way of removing children including event handlers + while (scope_ConnectBase.firstChild) { + scope_ConnectBase.removeChild(scope_ConnectBase.firstChild); + } + // Adding new connects according to the new connect options + for (var i = 0; i <= options.handles; i++) { + scope_Connects[i] = addConnect(scope_ConnectBase, options.connect[i]); + updateConnect(i); + } + // re-adding drag events for the new connect elements + // to ignore the other events we have to negate the 'if (!behaviour.fixed)' check + bindSliderEvents({ drag: options.events.drag, fixed: true }); + } + // Invert options for connect handles + function invertConnects() { + scope_ConnectsInverted = !scope_ConnectsInverted; + testConnect(options, + // inverse the connect boolean array + options.connect.map(function (b) { return !b; })); + updateConnectOption(); } // Initialization steps function setupSlider() { diff --git a/dist/nouislider.min.js b/dist/nouislider.min.js index d52d86a..978a4a3 100644 --- a/dist/nouislider.min.js +++ b/dist/nouislider.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).noUiSlider={})}(this,function(ot){"use strict";function n(t){return"object"==typeof t&&"function"==typeof t.to}function st(t){t.parentElement.removeChild(t)}function at(t){return null!=t}function lt(t){t.preventDefault()}function i(t){return"number"==typeof t&&!isNaN(t)&&isFinite(t)}function ut(t,e,r){0=e[r];)r+=1;return r}function r(t,e,r){if(r>=t.slice(-1)[0])return 100;var n=l(r,t),i=t[n-1],o=t[n],t=e[n-1],n=e[n];return t+(r=r,a(o=[i,o],o[0]<0?r+Math.abs(o[0]):r-o[0],0)/s(t,n))}function o(t,e,r,n){if(100===n)return n;var i=l(n,t),o=t[i-1],s=t[i];return r?(s-o)/2this.xPct[n+1];)n++;else t===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||t!==this.xPct[n+1]||n++;for(var i,o=1,s=(e=null===e?[]:e)[n],a=0,l=0,u=0,c=r?(t-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-t)/(this.xPct[n+1]-this.xPct[n]);0= 2) required for mode 'count'.");for(var e=t.values-1,r=100/e,n=[];e--;)n[e]=e*r;return n.push(100),U(n,t.stepped)}(d),m={},t=S.xVal[0],e=S.xVal[S.xVal.length-1],g=!1,v=!1,b=0;return(h=h.slice().sort(function(t,e){return t-e}).filter(function(t){return!this[t]&&(this[t]=!0)},{}))[0]!==t&&(h.unshift(t),g=!0),h[h.length-1]!==e&&(h.push(e),v=!0),h.forEach(function(t,e){var r,n,i,o,s,a,l,u,t=t,c=h[e+1],p=d.mode===ot.PipsMode.Steps,f=(f=p?S.xNumSteps[e]:f)||c-t;for(void 0===c&&(c=t),f=Math.max(f,1e-7),r=t;r<=c;r=Number((r+f).toFixed(7))){for(a=(o=(i=S.toStepping(r))-b)/(d.density||1),u=o/(l=Math.round(a)),n=1;n<=l;n+=1)m[(s=b+n*u).toFixed(5)]=[S.fromStepping(s),0];a=-1ot.PipsType.NoValue&&((t=P(a,!1)).className=p(n,f.cssClasses.value),t.setAttribute("data-value",String(r)),t.style[f.style]=e+"%",t.innerHTML=String(s.to(r))))}),a}function L(){n&&(st(n),n=null)}function T(t){L();var e=D(t),r=t.filter,t=t.format||{to:function(t){return String(Math.round(t))}};return n=d.appendChild(O(e,r,t))}function j(){var t=i.getBoundingClientRect(),e="offset"+["Width","Height"][f.ort];return 0===f.ort?t.width||i[e]:t.height||i[e]}function z(n,i,o,s){function e(t){var e,r=function(e,t,r){var n=0===e.type.indexOf("touch"),i=0===e.type.indexOf("mouse"),o=0===e.type.indexOf("pointer"),s=0,a=0;0===e.type.indexOf("MSPointer")&&(o=!0);if("mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(n){var l=function(t){t=t.target;return t===r||r.contains(t)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){n=Array.prototype.filter.call(e.touches,l);if(1r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),t=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===e?i=null:0===e&&(t=null);e=S.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(e))),[t=null!==t&&!1!==t?Number(t.toFixed(e)):t,i]}ft(t=d,f.cssClasses.target),0===f.dir?ft(t,f.cssClasses.ltr):ft(t,f.cssClasses.rtl),0===f.ort?ft(t,f.cssClasses.horizontal):ft(t,f.cssClasses.vertical),ft(t,"rtl"===getComputedStyle(t).direction?f.cssClasses.textDirectionRtl:f.cssClasses.textDirectionLtr),i=P(t,f.cssClasses.base),function(t,e){var r=P(e,f.cssClasses.connects);l=[],(a=[]).push(N(r,t[0]));for(var n=0;n=e[r];)r+=1;return r}function r(t,e,r){if(r>=t.slice(-1)[0])return 100;var n=l(r,t),i=t[n-1],o=t[n],t=e[n-1],n=e[n];return t+(r=r,a(o=[i,o],o[0]<0?r+Math.abs(o[0]):r-o[0],0)/s(t,n))}function o(t,e,r,n){if(100===n)return n;var i=l(n,t),o=t[i-1],s=t[i];return r?(s-o)/2this.xPct[n+1];)n++;else t===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||t!==this.xPct[n+1]||n++;for(var i,o=1,s=(e=null===e?[]:e)[n],a=0,l=0,u=0,c=r?(t-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-t)/(this.xPct[n+1]-this.xPct[n]);0= 2) required for mode 'count'.");for(var e=t.values-1,r=100/e,n=[];e--;)n[e]=e*r;return n.push(100),U(n,t.stepped)}(d),m={},t=S.xVal[0],e=S.xVal[S.xVal.length-1],g=!1,v=!1,b=0;return(h=h.slice().sort(function(t,e){return t-e}).filter(function(t){return!this[t]&&(this[t]=!0)},{}))[0]!==t&&(h.unshift(t),g=!0),h[h.length-1]!==e&&(h.push(e),v=!0),h.forEach(function(t,e){var r,n,i,o,s,a,l,u,t=t,c=h[e+1],p=d.mode===ut.PipsMode.Steps,f=(f=p?S.xNumSteps[e]:f)||c-t;for(void 0===c&&(c=t),f=Math.max(f,1e-7),r=t;r<=c;r=Number((r+f).toFixed(7))){for(a=(o=(i=S.toStepping(r))-b)/(d.density||1),u=o/(l=Math.round(a)),n=1;n<=l;n+=1)m[(s=b+n*u).toFixed(5)]=[S.fromStepping(s),0];a=-1ut.PipsType.NoValue&&((t=P(a,!1)).className=p(n,f.cssClasses.value),t.setAttribute("data-value",String(r)),t.style[f.style]=e+"%",t.innerHTML=String(s.to(r))))}),a}function L(){s&&(ct(s),s=null)}function T(t){L();var e=D(t),r=t.filter,t=t.format||{to:function(t){return String(Math.round(t))}};return s=d.appendChild(O(e,r,t))}function j(){var t=i.getBoundingClientRect(),e="offset"+["Width","Height"][f.ort];return 0===f.ort?t.width||i[e]:t.height||i[e]}function z(n,i,o,s){function e(t){var e,r=function(e,t,r){var n=0===e.type.indexOf("touch"),i=0===e.type.indexOf("mouse"),o=0===e.type.indexOf("pointer"),s=0,a=0;0===e.type.indexOf("MSPointer")&&(o=!0);if("mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(n){var l=function(t){t=t.target;return t===r||r.contains(t)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){n=Array.prototype.filter.call(e.touches,l);if(1=r[e-1]});if(x!==!e)return x=!x,xt(f,f.connect.map(function(t){return!t})),void at()}rt(t),rt(t+1),x&&(rt(t-1),rt(t+2))}function tt(){g.forEach(function(t){var e=50r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),t=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===e?i=null:0===e&&(t=null);e=S.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(e))),[t=null!==t&&!1!==t?Number(t.toFixed(e)):t,i]}function at(){for(;n.firstChild;)n.removeChild(n.firstChild);for(var t=0;t<=f.handles;t++)u[t]=N(n,f.connect[t]),rt(t);Y({drag:f.events.drag,fixed:!0})}gt(t=d,f.cssClasses.target),0===f.dir?gt(t,f.cssClasses.ltr):gt(t,f.cssClasses.rtl),0===f.ort?gt(t,f.cssClasses.horizontal):gt(t,f.cssClasses.vertical),gt(t,"rtl"===getComputedStyle(t).direction?f.cssClasses.textDirectionRtl:f.cssClasses.textDirectionLtr),i=P(t,f.cssClasses.base),function(t,e){n=P(e,f.cssClasses.connects),l=[],(u=[]).push(N(n,t[0]));for(var r=0;r=e[r];)r+=1;return r}function toStepping(t,e,r){if(r>=t.slice(-1)[0])return 100;var n=getJ(r,t),i=t[n-1],s=t[n],t=e[n-1],n=e[n];return t+toPercentage([i,s],r)/subRangeRatio(t,n)}function fromStepping(t,e,r){if(100<=r)return t.slice(-1)[0];var n=getJ(r,e),i=t[n-1],s=t[n],t=e[n-1];return isPercentage([i,s],(r-t)*subRangeRatio(t,e[n]))}function getStep(t,e,r,n){if(100===n)return n;var i=getJ(n,t),s=t[i-1],o=t[i];return r?(o-s)/2this.xPct[n+1];)n++;else t===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||t!==this.xPct[n+1]||n++;for(var i,s=1,o=(e=null===e?[]:e)[n],a=0,l=0,u=0,c=r?(t-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-t)/(this.xPct[n+1]-this.xPct[n]);0= 2) required for mode 'count'.");for(var e=t.values-1,r=100/e,n=[];e--;)n[e]=e*r;return n.push(100),M(n,t.stepped)}(f),m={},t=S.xVal[0],e=S.xVal[S.xVal.length-1],g=!1,v=!1,b=0;return(h=unique(h.slice().sort(function(t,e){return t-e})))[0]!==t&&(h.unshift(t),g=!0),h[h.length-1]!==e&&(h.push(e),v=!0),h.forEach(function(t,e){var r,n,i,s,o,a,l,u,t=t,c=h[e+1],p=f.mode===PipsMode.Steps,d=(d=p?S.xNumSteps[e]:d)||c-t;for(void 0===c&&(c=t),d=Math.max(d,1e-7),r=t;r<=c;r=Number((r+d).toFixed(7))){for(a=(s=(i=S.toStepping(r))-b)/(f.density||1),u=s/(l=Math.round(a)),n=1;n<=l;n+=1)m[(o=b+n*u).toFixed(5)]=[S.fromStepping(o),0];a=-1PipsType.NoValue&&((t=N(a,!1)).className=p(n,d.cssClasses.value),t.setAttribute("data-value",String(r)),t.style[d.style]=e+"%",t.innerHTML=String(o.to(r))))}),a}function U(){n&&(removeElement(n),n=null)}function L(t){U();var e=D(t),r=t.filter,t=t.format||{to:function(t){return String(Math.round(t))}};return n=f.appendChild(T(e,r,t))}function O(){var t=r.getBoundingClientRect(),e="offset"+["Width","Height"][d.ort];return 0===d.ort?t.width||r[e]:t.height||r[e]}function F(e,r,n,i){function s(t){return!!(t=function(e,t,r){var n=0===e.type.indexOf("touch"),i=0===e.type.indexOf("mouse"),s=0===e.type.indexOf("pointer"),o=0,a=0;0===e.type.indexOf("MSPointer")&&(s=!0);if("mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(n){var l=function(t){t=t.target;return t===r||r.contains(t)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){n=Array.prototype.filter.call(e.touches,l);if(1r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),t=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===e?i=null:0===e&&(t=null);e=S.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(e))),[t=null!==t&&!1!==t?Number(t.toFixed(e)):t,i]}addClass(t=f,d.cssClasses.target),0===d.dir?addClass(t,d.cssClasses.ltr):addClass(t,d.cssClasses.rtl),0===d.ort?addClass(t,d.cssClasses.horizontal):addClass(t,d.cssClasses.vertical),addClass(t,"rtl"===getComputedStyle(t).direction?d.cssClasses.textDirectionRtl:d.cssClasses.textDirectionLtr),r=N(t,d.cssClasses.base),function(t,e){var r=N(e,d.cssClasses.connects);l=[],(a=[]).push(w(r,t[0]));for(var n=0;n=e[r];)r+=1;return r}function toStepping(t,e,r){if(r>=t.slice(-1)[0])return 100;var n=getJ(r,t),i=t[n-1],s=t[n],t=e[n-1],n=e[n];return t+toPercentage([i,s],r)/subRangeRatio(t,n)}function fromStepping(t,e,r){if(100<=r)return t.slice(-1)[0];var n=getJ(r,e),i=t[n-1],s=t[n],t=e[n-1];return isPercentage([i,s],(r-t)*subRangeRatio(t,e[n]))}function getStep(t,e,r,n){if(100===n)return n;var i=getJ(n,t),s=t[i-1],o=t[i];return r?(o-s)/2this.xPct[n+1];)n++;else t===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||t!==this.xPct[n+1]||n++;for(var i,s=1,o=(e=null===e?[]:e)[n],a=0,l=0,u=0,c=r?(t-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-t)/(this.xPct[n+1]-this.xPct[n]);0= 2) required for mode 'count'.");for(var e=t.values-1,r=100/e,n=[];e--;)n[e]=e*r;return n.push(100),D(n,t.stepped)}(f),m={},t=S.xVal[0],e=S.xVal[S.xVal.length-1],g=!1,v=!1,b=0;return(h=unique(h.slice().sort(function(t,e){return t-e})))[0]!==t&&(h.unshift(t),g=!0),h[h.length-1]!==e&&(h.push(e),v=!0),h.forEach(function(t,e){var r,n,i,s,o,a,l,u,t=t,c=h[e+1],p=f.mode===PipsMode.Steps,d=(d=p?S.xNumSteps[e]:d)||c-t;for(void 0===c&&(c=t),d=Math.max(d,1e-7),r=t;r<=c;r=Number((r+d).toFixed(7))){for(a=(s=(i=S.toStepping(r))-b)/(f.density||1),u=s/(l=Math.round(a)),n=1;n<=l;n+=1)m[(o=b+n*u).toFixed(5)]=[S.fromStepping(o),0];a=-1PipsType.NoValue&&((t=C(a,!1)).className=p(n,d.cssClasses.value),t.setAttribute("data-value",String(r)),t.style[d.style]=e+"%",t.innerHTML=String(o.to(r))))}),a}function L(){i&&(removeElement(i),i=null)}function O(t){L();var e=T(t),r=t.filter,t=t.format||{to:function(t){return String(Math.round(t))}};return i=p.appendChild(U(e,r,t))}function F(){var t=r.getBoundingClientRect(),e="offset"+["Width","Height"][d.ort];return 0===d.ort?t.width||r[e]:t.height||r[e]}function R(e,r,n,i){function s(t){return!!(t=function(e,t,r){var n=0===e.type.indexOf("touch"),i=0===e.type.indexOf("mouse"),s=0===e.type.indexOf("pointer"),o=0,a=0;0===e.type.indexOf("MSPointer")&&(s=!0);if("mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(n){var l=function(t){t=t.target;return t===r||r.contains(t)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){n=Array.prototype.filter.call(e.touches,l);if(1=r[e-1]});if(b!==!e)return b=!b,testConnect(d,d.connect.map(function(t){return!t})),void at()}rt(t),rt(t+1),b&&(rt(t-1),rt(t+2))}function tt(){m.forEach(function(t){var e=50r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),t=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===e?i=null:0===e&&(t=null);e=S.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(e))),[t=null!==t&&!1!==t?Number(t.toFixed(e)):t,i]}function at(){for(;n.firstChild;)n.removeChild(n.firstChild);for(var t=0;t<=d.handles;t++)u[t]=P(n,d.connect[t]),rt(t);q({drag:d.events.drag,fixed:!0})}addClass(t=p,d.cssClasses.target),0===d.dir?addClass(t,d.cssClasses.ltr):addClass(t,d.cssClasses.rtl),0===d.ort?addClass(t,d.cssClasses.horizontal):addClass(t,d.cssClasses.vertical),addClass(t,"rtl"===getComputedStyle(t).direction?d.cssClasses.textDirectionRtl:d.cssClasses.textDirectionLtr),r=C(t,d.cssClasses.base),function(t,e){n=C(e,d.cssClasses.connects),l=[],(u=[]).push(P(n,t[0]));for(var r=0;r= 0; var hover = entry.indexOf("hover") >= 0; var unconstrained = entry.indexOf("unconstrained") >= 0; + var invertConnects = entry.indexOf("invert-connects") >= 0; var dragAll = entry.indexOf("drag-all") >= 0; var smoothSteps = entry.indexOf("smooth-steps") >= 0; if (fixed) { @@ -717,6 +718,9 @@ function testBehaviour(parsed, entry) { // Use margin to enforce fixed state testMargin(parsed, parsed.start[1] - parsed.start[0]); } + if (invertConnects && parsed.handles !== 2) { + throw new Error("noUiSlider: 'invert-connects' behaviour must be used with 2 handles"); + } if (unconstrained && (parsed.margin || parsed.limit)) { throw new Error("noUiSlider: 'unconstrained' behaviour cannot be used with margin or limit"); } @@ -729,6 +733,7 @@ function testBehaviour(parsed, entry) { snap: snap, hover: hover, unconstrained: unconstrained, + invertConnects: invertConnects, }; } function testTooltips(parsed, entry) { @@ -899,6 +904,7 @@ function scope(target, options, originalOptions) { // Slider DOM Nodes var scope_Target = target; var scope_Base; + var scope_ConnectBase; var scope_Handles; var scope_Connects; var scope_Pips; @@ -910,6 +916,7 @@ function scope(target, options, originalOptions) { var scope_HandleNumbers = []; var scope_ActiveHandlesCount = 0; var scope_Events = {}; + var scope_ConnectsInverted = false; // Document Nodes var scope_Document = target.ownerDocument; var scope_DocumentElement = options.documentElement || scope_Document.documentElement; @@ -966,17 +973,17 @@ function scope(target, options, originalOptions) { } // Add handles to the slider base. function addElements(connectOptions, base) { - var connectBase = addNodeTo(base, options.cssClasses.connects); + scope_ConnectBase = addNodeTo(base, options.cssClasses.connects); scope_Handles = []; scope_Connects = []; - scope_Connects.push(addConnect(connectBase, connectOptions[0])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[0])); // [::::O====O====O====] // connectOptions = [0, 1, 1, 1] for (var i = 0; i < options.handles; i++) { // Keep a list of all added handles. scope_Handles.push(addOrigin(base, i)); scope_HandleNumbers[i] = i; - scope_Connects.push(addConnect(connectBase, connectOptions[i + 1])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[i + 1])); } } // Initialize a single slider. @@ -1925,8 +1932,26 @@ function scope(target, options, originalOptions) { var translation = transformDirection(to, 0) - scope_DirOffset; var translateRule = "translate(" + inRuleOrder(translation + "%", "0") + ")"; scope_Handles[handleNumber].style[options.transformRule] = translateRule; + // sanity check for at least 2 handles (e.g. during setup) + if (options.events.invertConnects && scope_Locations.length > 1) { + // check if handles passed each other, but don't match the ConnectsInverted state + var handlesAreInOrder = scope_Locations.every(function (position, index, locations) { + return index === 0 || position >= locations[index - 1]; + }); + if (scope_ConnectsInverted !== !handlesAreInOrder) { + // invert connects when handles pass each other + invertConnects(); + // invertConnects already updates all connect elements + return; + } + } updateConnect(handleNumber); updateConnect(handleNumber + 1); + if (scope_ConnectsInverted) { + // When connects are inverted, we also have to update adjacent connects + updateConnect(handleNumber - 1); + updateConnect(handleNumber + 2); + } } // Handles before the slider middle are stacked later = higher, // Handles after the middle later is lower @@ -1956,13 +1981,20 @@ function scope(target, options, originalOptions) { if (!scope_Connects[index]) { return; } + // Create a copy of locations, so we can sort them for the local scope logic + var locations = scope_Locations.slice(); + if (scope_ConnectsInverted) { + locations.sort(function (a, b) { + return a - b; + }); + } var l = 0; var h = 100; if (index !== 0) { - l = scope_Locations[index - 1]; + l = locations[index - 1]; } if (index !== scope_Connects.length - 1) { - h = scope_Locations[index]; + h = locations[index]; } // We use two rules: // 'translate' to change the left/top offset; @@ -2154,6 +2186,7 @@ function scope(target, options, originalOptions) { "format", "pips", "tooltips", + "connect", ]; // Only change options that we're actually passed to update. updateAble.forEach(function (name) { @@ -2191,6 +2224,32 @@ function scope(target, options, originalOptions) { // Invalidate the current positioning so valueSet forces an update. scope_Locations = []; valueSet(isSet(optionsToUpdate.start) ? optionsToUpdate.start : v, fireSetEvent); + // Update connects only if it was set + if (optionsToUpdate.connect) { + updateConnectOption(); + } + } + function updateConnectOption() { + // IE supported way of removing children including event handlers + while (scope_ConnectBase.firstChild) { + scope_ConnectBase.removeChild(scope_ConnectBase.firstChild); + } + // Adding new connects according to the new connect options + for (var i = 0; i <= options.handles; i++) { + scope_Connects[i] = addConnect(scope_ConnectBase, options.connect[i]); + updateConnect(i); + } + // re-adding drag events for the new connect elements + // to ignore the other events we have to negate the 'if (!behaviour.fixed)' check + bindSliderEvents({ drag: options.events.drag, fixed: true }); + } + // Invert options for connect handles + function invertConnects() { + scope_ConnectsInverted = !scope_ConnectsInverted; + testConnect(options, + // inverse the connect boolean array + options.connect.map(function (b) { return !b; })); + updateConnectOption(); } // Initialization steps function setupSlider() { diff --git a/package-lock.json b/package-lock.json index 7712f42..48bee54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nouislider", - "version": "15.7.2", + "version": "15.8.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nouislider", - "version": "15.7.2", + "version": "15.8.0", "license": "MIT", "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.10", diff --git a/package.json b/package.json index d864294..ae9d817 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nouislider", - "version": "15.7.2", + "version": "15.8.0", "main": "dist/nouislider.js", "module": "dist/nouislider.mjs", "style": "dist/nouislider.min.css", From fc0c972fe1697970d27b60da62d833036e7b4cdb Mon Sep 17 00:00:00 2001 From: Gudine Date: Thu, 20 Jun 2024 16:37:08 -0300 Subject: [PATCH 6/9] Converts getTooltips and getOrigins return types into arrays --- src/nouislider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nouislider.ts b/src/nouislider.ts index e7ee831..131c56a 100644 --- a/src/nouislider.ts +++ b/src/nouislider.ts @@ -216,8 +216,8 @@ export interface API { removePips: () => void; removeTooltips: () => void; getPositions: () => number[]; - getTooltips: () => { [handleNumber: number]: HTMLElement | false }; - getOrigins: () => { [handleNumber: number]: HTMLElement }; + getTooltips: () => (HTMLElement | false)[] | null; + getOrigins: () => HTMLElement[]; pips: (grid: Pips) => HTMLElement; } From d53446cb7e2bc35c0e6a49f259321a423120b0ae Mon Sep 17 00:00:00 2001 From: lgersen Date: Fri, 21 Jun 2024 16:14:14 +0200 Subject: [PATCH 7/9] Better example for center-fixed sliders --- documentation/examples-content/from-center.php | 13 ++++++++++--- documentation/examples/from-center.css | 3 +++ documentation/examples/from-center.js | 11 +++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 documentation/examples/from-center.css create mode 100644 documentation/examples/from-center.js diff --git a/documentation/examples-content/from-center.php b/documentation/examples-content/from-center.php index ee998d2..59ddf4b 100644 --- a/documentation/examples-content/from-center.php +++ b/documentation/examples-content/from-center.php @@ -5,12 +5,19 @@
    -

    Issue #371 requested a slider with the connect option originating from the slider center.

    - -

    An example of how to implement this is available as a JSFiddle.

    +

    Issue #371 requested a slider with the connect option originating from the slider center. + This can be implemented by hiding one of the handles and using the "unconstrained" behaviour.

    +
    +
    + + +
    + + +
    diff --git a/documentation/examples/from-center.css b/documentation/examples/from-center.css new file mode 100644 index 0000000..46126fe --- /dev/null +++ b/documentation/examples/from-center.css @@ -0,0 +1,3 @@ +#from-center .noUi-handle[data-handle="0"] { + display: none; +} diff --git a/documentation/examples/from-center.js b/documentation/examples/from-center.js new file mode 100644 index 0000000..9289219 --- /dev/null +++ b/documentation/examples/from-center.js @@ -0,0 +1,11 @@ +var fromCenterSlider = document.getElementById('from-center'); + +noUiSlider.create(fromCenterSlider, { + start: [50, 80], + behaviour: 'unconstrained', + connect: true, + range: { + 'min': 0, + 'max': 100 + } +}); From b8a1b58b1019f3562712d090a515de76c63c208c Mon Sep 17 00:00:00 2001 From: lgersen Date: Fri, 21 Jun 2024 16:17:18 +0200 Subject: [PATCH 8/9] noUiSlider 15.8.1 --- CHANGELOG.MD | 3 +++ dist/nouislider.d.ts | 8 ++------ package-lock.json | 32 ++++++++++++++++---------------- package.json | 2 +- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index de62755..7dcc01f 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,5 +1,8 @@ # Changelog +### 15.8.1 (*2024-06-21*) +- Fixed: Typescript definitions for `getTooltips` and `getOrigins` (#1273); + ### 15.8.0 (*2024-06-10*) - Added: Allow `connect` option to get updated (#1272); - Added: `invert-connects` behaviour (#1262, #1272); diff --git a/dist/nouislider.d.ts b/dist/nouislider.d.ts index 9cb9e12..827908f 100644 --- a/dist/nouislider.d.ts +++ b/dist/nouislider.d.ts @@ -143,12 +143,8 @@ export interface API { removePips: () => void; removeTooltips: () => void; getPositions: () => number[]; - getTooltips: () => { - [handleNumber: number]: HTMLElement | false; - }; - getOrigins: () => { - [handleNumber: number]: HTMLElement; - }; + getTooltips: () => (HTMLElement | false)[] | null; + getOrigins: () => HTMLElement[]; pips: (grid: Pips) => HTMLElement; } interface TargetElement extends HTMLElement { diff --git a/package-lock.json b/package-lock.json index 48bee54..6f573af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nouislider", - "version": "15.8.0", + "version": "15.8.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nouislider", - "version": "15.8.0", + "version": "15.8.1", "license": "MIT", "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.10", @@ -460,12 +460,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -932,9 +932,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2535,12 +2535,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "callsites": { @@ -2903,9 +2903,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" diff --git a/package.json b/package.json index ae9d817..eb40fa1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nouislider", - "version": "15.8.0", + "version": "15.8.1", "main": "dist/nouislider.js", "module": "dist/nouislider.mjs", "style": "dist/nouislider.min.css", From b36d0182f88543d3fe82ad35dc50770b5f331f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Fri, 21 Jun 2024 17:39:35 +0000 Subject: [PATCH 9/9] Fix typos --- documentation/assets/wNumb.js | 2 +- documentation/behaviour-option.php | 2 +- documentation/download.php | 2 +- documentation/events-callbacks.php | 2 +- documentation/examples-content/styling.php | 2 +- src/nouislider.ts | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/documentation/assets/wNumb.js b/documentation/assets/wNumb.js index 4ab340f..e8f46f7 100644 --- a/documentation/assets/wNumb.js +++ b/documentation/assets/wNumb.js @@ -232,7 +232,7 @@ var FormatOptions = [ return false; } - // Covert to number. + // Convert to number. output = Number(output); // Run the user-specified post-decoder. diff --git a/documentation/behaviour-option.php b/documentation/behaviour-option.php index e242046..6ff47a8 100644 --- a/documentation/behaviour-option.php +++ b/documentation/behaviour-option.php @@ -171,7 +171,7 @@
    -

    A handle snaps to a clicked location. It can immediatly be moved, without a mouseup + mousedown.

    +

    A handle snaps to a clicked location. It can immediately be moved, without a mouseup + mousedown.

    diff --git a/documentation/download.php b/documentation/download.php index 53d71ee..d083d84 100644 --- a/documentation/download.php +++ b/documentation/download.php @@ -30,6 +30,6 @@

    noUiSlider can be downloaded from GitHub. Required files are in the dist folder. To compile from sources, run npm run build.

    - Download noUiSlider from Github + Download noUiSlider from GitHub
    diff --git a/documentation/events-callbacks.php b/documentation/events-callbacks.php index f90c4c8..dc57c92 100644 --- a/documentation/events-callbacks.php +++ b/documentation/events-callbacks.php @@ -157,7 +157,7 @@

    noUiSlider uses a custom binding model with support for namespaces. There are two methods, .on(eventName, callback) and .off(eventName). Events can be namespaced by appending a period ('.') and an identifier to the event name.

    -

    Nested namespaces ('slide.something.else') are not supported, and are threated as a single namespace (so '.a.b' isn't related to '.a').

    +

    Nested namespaces ('slide.something.else') are not supported, and are treated as a single namespace (so '.a.b' isn't related to '.a').

    values is an array containing the current slider values, with formatting applied. handle is the index of the handle that caused the event, starting at zero. values[handle] gives the value for the handle that triggered the event.

    diff --git a/documentation/examples-content/styling.php b/documentation/examples-content/styling.php index 38c302f..b1b6406 100644 --- a/documentation/examples-content/styling.php +++ b/documentation/examples-content/styling.php @@ -45,7 +45,7 @@
    -
    Fit handles withing the slider
    +
    Fit handles within the slider
    diff --git a/src/nouislider.ts b/src/nouislider.ts index 131c56a..adf0ea7 100644 --- a/src/nouislider.ts +++ b/src/nouislider.ts @@ -342,7 +342,7 @@ function offset(elem: HTMLElement, orientation: 0 | 1): number { // getBoundingClientRect contains left scroll in Chrome on Android. // I haven't found a feature detection that proves this. Worst case - // scenario on mis-match: the 'tap' feature on horizontal sliders breaks. + // scenario on mismatch: the 'tap' feature on horizontal sliders breaks. if (/webkit.*Chrome.*Mobile/i.test(navigator.userAgent)) { pageOffset.x = 0; } @@ -776,7 +776,7 @@ class Spectrum { private handleEntryPoint(index: string, value: WrappedSubRange): void { let percentage; - // Covert min/max syntax to 0 and 100. + // Convert min/max syntax to 0 and 100. if (index === "min") { percentage = 0; } else if (index === "max") {