Skip to content

Commit 077c436

Browse files
authored
Merge pull request #1272 from 5andr0/master
Inverted Connects for unconstrained behavior
2 parents ae93f93 + 3406bc1 commit 077c436

File tree

5 files changed

+135
-9
lines changed

5 files changed

+135
-9
lines changed

documentation/behaviour-option.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
<div class="view">
1212

13-
<p>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 <code>behaviour</code> option.</p>
13+
<p>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 <code>behaviour</code> option.</p>
1414

15-
<p>This option accepts a <code>"-"</code> separated list of <code>"drag"</code>, <code>"drag-all"</code>, <code>"tap"</code>, <code>"fixed"</code>, <code>"snap"</code>, <code>"unconstrained"</code> or <code>"none"</code>.</p>
15+
<p>This option accepts a <code>"-"</code> separated list of <code>"drag"</code>, <code>"drag-all"</code>, <code>"tap"</code>, <code>"fixed"</code>, <code>"snap"</code>, <code>"unconstrained"</code>, <code>"invert-connects"</code> or <code>"none"</code>.</p>
1616

1717
<div class="example">
1818
<div id="behaviour"></div>
@@ -36,6 +36,7 @@
3636
<li><a href="#section-hover">Hover</a></li>
3737
<li><a href="#section-unconstrained">Unconstrained</a></li>
3838
<li><a href="#section-smooth-steps">Smooth steps</a></li>
39+
<li><a href="#section-invert-connects">Invert connects</a></li>
3940
</ul>
4041
</section>
4142

@@ -262,3 +263,32 @@
262263
<?php code('combined'); ?>
263264
</div>
264265
</section>
266+
267+
268+
<?php sect('invert-connects'); ?>
269+
<h2>Invert Connects</h2>
270+
271+
<section>
272+
273+
<div class="view">
274+
<p>With this option set, connects invert when handles pass each other.</p>
275+
276+
<p>Requires the <code><a href="#section-unconstrained">unconstrained</a></code> behaviour and the <code>connect</code> option. This option is only applicable for sliders with two handles.</p>
277+
<div class="example">
278+
<div id="invert-connects"></div>
279+
<span class="example-val" id="invert-connects-values"></span>
280+
<?php run('invert-connects'); ?>
281+
<?php run('invert-connects-link'); ?>
282+
</div>
283+
</div>
284+
285+
<div class="side">
286+
<?php code('invert-connects'); ?>
287+
288+
<div class="viewer-header">Show the slider value</div>
289+
290+
<div class="viewer-content">
291+
<?php code('invert-connects-link'); ?>
292+
</div>
293+
</div>
294+
</section>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
var invertConnectsValues = document.getElementById('invert-connects-values');
2+
3+
invertConnectsSlider.noUiSlider.on('update', function (values) {
4+
var minToHHMM = function(mins) {
5+
var pad = function(n) { return ('0'+n).slice(-2); };
6+
return [mins / 60 ^ 0, mins % 60].map(pad).join(':');
7+
};
8+
invertConnectsValues.innerHTML = values.map(minToHHMM).join(' - ');
9+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
var invertConnectsSlider = document.getElementById('invert-connects');
2+
3+
noUiSlider.create(invertConnectsSlider, {
4+
start: [20*60, 8*60],
5+
behaviour: 'unconstrained-invert-connects',
6+
step: 15,
7+
connect: true,
8+
range: {
9+
'min': 0,
10+
'max': 24*60
11+
}
12+
});

documentation/more.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575

7676
<div class="view">
7777

78-
<p>noUiSlider has an update method that can change the <code>'margin'</code>, <code>'padding'</code>, <code>'limit'</code>, <code>'step'</code>, <code>'range'</code>, <code>'pips'</code>, <code>'tooltips'</code>, <code>'animate'</code> and <code>'snap'</code> options.</p>
78+
<p>noUiSlider has an update method that can change the <code>'margin'</code>, <code>'padding'</code>, <code>'limit'</code>, <code>'step'</code>, <code>'range'</code>, <code>'pips'</code>, <code>'tooltips'</code>, <code>'connect'</code>, <code>'animate'</code> and <code>'snap'</code> options.</p>
7979

8080
<p>All other options require changes to the slider's HTML or event bindings.</p>
8181

src/nouislider.ts

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,11 @@ interface UpdatableOptions {
131131
format?: Formatter;
132132
tooltips?: boolean | PartialFormatter | (boolean | PartialFormatter)[];
133133
animate?: boolean;
134+
connect?: "lower" | "upper" | boolean | boolean[];
134135
}
135136

136137
export interface Options extends UpdatableOptions {
137138
range: Range;
138-
connect?: "lower" | "upper" | boolean | boolean[];
139139
orientation?: "vertical" | "horizontal";
140140
direction?: "ltr" | "rtl";
141141
behaviour?: string;
@@ -160,6 +160,7 @@ interface Behaviour {
160160
snap: boolean;
161161
hover: boolean;
162162
unconstrained: boolean;
163+
invertConnects: boolean;
163164
}
164165

165166
interface ParsedOptions {
@@ -1136,6 +1137,7 @@ function testBehaviour(parsed: ParsedOptions, entry: unknown): void {
11361137
const snap = entry.indexOf("snap") >= 0;
11371138
const hover = entry.indexOf("hover") >= 0;
11381139
const unconstrained = entry.indexOf("unconstrained") >= 0;
1140+
const invertConnects = entry.indexOf("invert-connects") >= 0;
11391141
const dragAll = entry.indexOf("drag-all") >= 0;
11401142
const smoothSteps = entry.indexOf("smooth-steps") >= 0;
11411143

@@ -1148,6 +1150,10 @@ function testBehaviour(parsed: ParsedOptions, entry: unknown): void {
11481150
testMargin(parsed, parsed.start[1] - parsed.start[0]);
11491151
}
11501152

1153+
if (invertConnects && parsed.handles !== 2) {
1154+
throw new Error("noUiSlider: 'invert-connects' behaviour must be used with 2 handles");
1155+
}
1156+
11511157
if (unconstrained && (parsed.margin || parsed.limit)) {
11521158
throw new Error("noUiSlider: 'unconstrained' behaviour cannot be used with margin or limit");
11531159
}
@@ -1161,6 +1167,7 @@ function testBehaviour(parsed: ParsedOptions, entry: unknown): void {
11611167
snap: snap,
11621168
hover: hover,
11631169
unconstrained: unconstrained,
1170+
invertConnects: invertConnects,
11641171
};
11651172
}
11661173

@@ -1367,6 +1374,7 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O
13671374
// Slider DOM Nodes
13681375
const scope_Target = target;
13691376
let scope_Base: HTMLElement;
1377+
let scope_ConnectBase: HTMLElement;
13701378
let scope_Handles: Origin[];
13711379
let scope_Connects: (HTMLElement | false)[];
13721380
let scope_Pips: HTMLElement | null;
@@ -1379,6 +1387,7 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O
13791387
const scope_HandleNumbers: number[] = [];
13801388
let scope_ActiveHandlesCount = 0;
13811389
const scope_Events: { [key: string]: EventCallback[] } = {};
1390+
let scope_ConnectsInverted = false;
13821391

13831392
// Document Nodes
13841393
const scope_Document = target.ownerDocument;
@@ -1452,12 +1461,12 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O
14521461

14531462
// Add handles to the slider base.
14541463
function addElements(connectOptions: boolean[], base: HTMLElement): void {
1455-
const connectBase = addNodeTo(base, options.cssClasses.connects);
1464+
scope_ConnectBase = addNodeTo(base, options.cssClasses.connects);
14561465

14571466
scope_Handles = [];
14581467
scope_Connects = [];
14591468

1460-
scope_Connects.push(addConnect(connectBase, connectOptions[0]));
1469+
scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[0]));
14611470

14621471
// [::::O====O====O====]
14631472
// connectOptions = [0, 1, 1, 1]
@@ -1466,7 +1475,7 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O
14661475
// Keep a list of all added handles.
14671476
scope_Handles.push(addOrigin(base, i));
14681477
scope_HandleNumbers[i] = i;
1469-
scope_Connects.push(addConnect(connectBase, connectOptions[i + 1]));
1478+
scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[i + 1]));
14701479
}
14711480
}
14721481

@@ -2666,8 +2675,31 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O
26662675

26672676
(scope_Handles[handleNumber].style as CSSStyleDeclarationIE10)[options.transformRule] = translateRule;
26682677

2678+
// sanity check for at least 2 handles (e.g. during setup)
2679+
if (options.events.invertConnects && scope_Locations.length > 1) {
2680+
// check if handles passed each other, but don't match the ConnectsInverted state
2681+
const handlesAreInOrder = scope_Locations.every(
2682+
(position: number, index: number, locations: number[]): boolean =>
2683+
index === 0 || position >= locations[index - 1]
2684+
);
2685+
2686+
if (scope_ConnectsInverted !== !handlesAreInOrder) {
2687+
// invert connects when handles pass each other
2688+
invertConnects();
2689+
2690+
// invertConnects already updates all connect elements
2691+
return;
2692+
}
2693+
}
2694+
26692695
updateConnect(handleNumber);
26702696
updateConnect(handleNumber + 1);
2697+
2698+
if (scope_ConnectsInverted) {
2699+
// When connects are inverted, we also have to update adjacent connects
2700+
updateConnect(handleNumber - 1);
2701+
updateConnect(handleNumber + 2);
2702+
}
26712703
}
26722704

26732705
// Handles before the slider middle are stacked later = higher,
@@ -2719,15 +2751,24 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O
27192751
return;
27202752
}
27212753

2754+
// Create a copy of locations, so we can sort them for the local scope logic
2755+
const locations = scope_Locations.slice();
2756+
2757+
if (scope_ConnectsInverted) {
2758+
locations.sort(function (a, b) {
2759+
return a - b;
2760+
});
2761+
}
2762+
27222763
let l = 0;
27232764
let h = 100;
27242765

27252766
if (index !== 0) {
2726-
l = scope_Locations[index - 1];
2767+
l = locations[index - 1];
27272768
}
27282769

27292770
if (index !== scope_Connects.length - 1) {
2730-
h = scope_Locations[index];
2771+
h = locations[index];
27312772
}
27322773

27332774
// We use two rules:
@@ -2968,6 +3009,7 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O
29683009
"format",
29693010
"pips",
29703011
"tooltips",
3012+
"connect",
29713013
];
29723014

29733015
// Only change options that we're actually passed to update.
@@ -3012,6 +3054,39 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O
30123054
scope_Locations = [];
30133055

30143056
valueSet(isSet(optionsToUpdate.start) ? optionsToUpdate.start : v, fireSetEvent);
3057+
3058+
// Update connects only if it was set
3059+
if (optionsToUpdate.connect) {
3060+
updateConnectOption();
3061+
}
3062+
}
3063+
3064+
function updateConnectOption() {
3065+
// IE supported way of removing children including event handlers
3066+
while (scope_ConnectBase.firstChild) {
3067+
scope_ConnectBase.removeChild(scope_ConnectBase.firstChild);
3068+
}
3069+
3070+
// Adding new connects according to the new connect options
3071+
for (let i = 0; i <= options.handles; i++) {
3072+
scope_Connects[i] = addConnect(scope_ConnectBase, options.connect[i]);
3073+
updateConnect(i);
3074+
}
3075+
3076+
// re-adding drag events for the new connect elements
3077+
// to ignore the other events we have to negate the 'if (!behaviour.fixed)' check
3078+
bindSliderEvents({ drag: options.events.drag, fixed: true } as Behaviour);
3079+
}
3080+
3081+
// Invert options for connect handles
3082+
function invertConnects() {
3083+
scope_ConnectsInverted = !scope_ConnectsInverted;
3084+
testConnect(
3085+
options,
3086+
// inverse the connect boolean array
3087+
options.connect.map((b: boolean) => !b)
3088+
);
3089+
updateConnectOption();
30153090
}
30163091

30173092
// Initialization steps

0 commit comments

Comments
 (0)