Skip to content

Commit

Permalink
Inverted Connects for unconstrained behavior
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
5andr0 committed Jun 10, 2024
1 parent ae93f93 commit 4697c18
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 6 deletions.
30 changes: 30 additions & 0 deletions documentation/behaviour-option.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<li><a href="#section-hover">Hover</a></li>
<li><a href="#section-unconstrained">Unconstrained</a></li>
<li><a href="#section-smooth-steps">Smooth steps</a></li>
<li><a href="#section-invert-connects">Invert connects</a></li>
</ul>
</section>

Expand Down Expand Up @@ -262,3 +263,32 @@
<?php code('combined'); ?>
</div>
</section>


<?php sect('invert-connects'); ?>
<h2>Invert Connects</h2>

<section>

<div class="view">
<p>With this option set, connects invert when handles pass each other.</p>

<p>Requires the <code><a href="#section-unconstrained">unconstrained</a></code> behaviour and the <code>connect</code> option.</p>
<div class="example">
<div id="invert-connects"></div>
<span class="example-val" id="invert-connects-values"></span>
<?php run('invert-connects'); ?>
<?php run('invert-connects-link'); ?>
</div>
</div>

<div class="side">
<?php code('invert-connects'); ?>

<div class="viewer-header">Show the slider value</div>

<div class="viewer-content">
<?php code('invert-connects-link'); ?>
</div>
</div>
</section>
9 changes: 9 additions & 0 deletions documentation/behaviour-option/invert-connects-link.js
Original file line number Diff line number Diff line change
@@ -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(' - ');
});
12 changes: 12 additions & 0 deletions documentation/behaviour-option/invert-connects.js
Original file line number Diff line number Diff line change
@@ -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
}
});
72 changes: 66 additions & 6 deletions src/nouislider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -160,6 +160,7 @@ interface Behaviour {
snap: boolean;
hover: boolean;
unconstrained: boolean;
invertConnects: boolean;
}

interface ParsedOptions {
Expand Down Expand Up @@ -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;

Expand All @@ -1161,6 +1163,7 @@ function testBehaviour(parsed: ParsedOptions, entry: unknown): void {
snap: snap,
hover: hover,
unconstrained: unconstrained,
invertConnects: invertConnects,
};
}

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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]
Expand All @@ -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]));
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 4697c18

Please sign in to comment.