Skip to content

Commit

Permalink
Merge pull request #1272 from 5andr0/master
Browse files Browse the repository at this point in the history
Inverted Connects for unconstrained behavior
  • Loading branch information
leongersen authored Jun 10, 2024
2 parents ae93f93 + 3406bc1 commit 077c436
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 9 deletions.
34 changes: 32 additions & 2 deletions documentation/behaviour-option.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

<div class="view">

<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>
<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>

<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>
<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>

<div class="example">
<div id="behaviour"></div>
Expand All @@ -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. This option is only applicable for sliders with two handles.</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
}
});
2 changes: 1 addition & 1 deletion documentation/more.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@

<div class="view">

<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>
<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>

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

Expand Down
87 changes: 81 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 @@ -1148,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");
}
Expand All @@ -1161,6 +1167,7 @@ function testBehaviour(parsed: ParsedOptions, entry: unknown): void {
snap: snap,
hover: hover,
unconstrained: unconstrained,
invertConnects: invertConnects,
};
}

Expand Down Expand Up @@ -1367,6 +1374,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 +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 = false;

// Document Nodes
const scope_Document = target.ownerDocument;
Expand Down Expand Up @@ -1452,12 +1461,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 +1475,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 +2675,31 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O

(scope_Handles[handleNumber].style as CSSStyleDeclarationIE10)[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
const handlesAreInOrder = scope_Locations.every(
(position: number, index: number, locations: number[]): boolean =>
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,
Expand Down Expand Up @@ -2719,15 +2751,24 @@ function scope(target: TargetElement, options: ParsedOptions, originalOptions: O
return;
}

// Create a copy of locations, so we can sort them for the local scope logic
const 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 +3009,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 +3054,39 @@ 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) {
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 (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;
testConnect(
options,
// inverse the connect boolean array
options.connect.map((b: boolean) => !b)
);
updateConnectOption();
}

// Initialization steps
Expand Down

0 comments on commit 077c436

Please sign in to comment.