Skip to content

Commit 933897d

Browse files
authored
Merge pull request #1150 from Patternslib/scrum-1106--review
Implement review comments
2 parents c2e1dad + 818c68d commit 933897d

File tree

7 files changed

+151
-50
lines changed

7 files changed

+151
-50
lines changed

src/core/utils.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ function parseTime(time) {
447447
* parseLength - Parse a length from a string and return the parsed length in
448448
* pixels.
449449
450-
* @param {String} length - A length string like `1px` or `25%`.
450+
* @param {String} length - A length string like `1px` or `25%`. Lengths without a unit are treated as pixels.
451451
* @param {Number} reference_length - The reference length to use for percentage lengths.
452452
*
453453
* @returns {Number} - A integer which represents the parsed length in pixels.
@@ -486,7 +486,7 @@ function parseLength(length, reference_length = null) {
486486
(amount * Math.max(window.innerWidth, window.innerHeight)) / 100
487487
);
488488
default:
489-
return null;
489+
return Math.round(amount);
490490
}
491491
}
492492

@@ -756,6 +756,36 @@ const threshold_list = (num_steps = 0) => {
756756
return thresholds.sort();
757757
};
758758

759+
/**
760+
* is_option_truthy - Check if an Pattern option is set.
761+
*
762+
* An option is set if it is not one of:
763+
* - undefined
764+
* - null
765+
* - "none"
766+
* - ""
767+
*
768+
* @param {String} option - The option to check.
769+
*
770+
* @returns {Boolean} - Returns true if the option is set, false otherwise.
771+
*
772+
* @example
773+
*
774+
* is_option_truthy() // false
775+
* is_option_truthy(undefined) // false
776+
* is_option_truthy(null) // false
777+
* is_option_truthy("") // false
778+
* is_option_truthy("none") // false
779+
* is_option_truthy("false") // false
780+
* is_option_truthy("foo") // true
781+
* is_option_truthy(true) // true
782+
* is_option_truthy(0) // true
783+
*
784+
*/
785+
const is_option_truthy = (option) => {
786+
return ![undefined, null, "none", false, "false", ""].includes(option);
787+
};
788+
759789
var utils = {
760790
jqueryPlugin: jqueryPlugin,
761791
escapeRegExp: escapeRegExp,
@@ -789,6 +819,7 @@ var utils = {
789819
is_iso_date: is_iso_date,
790820
date_diff: date_diff,
791821
threshold_list: threshold_list,
822+
is_option_truthy: is_option_truthy,
792823
getCSSValue: dom.get_css_value, // BBB: moved to dom. TODO: Remove in upcoming version.
793824
};
794825

src/core/utils.test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,24 @@ describe("parseLength", function () {
480480
expect(p).toThrow();
481481
});
482482

483+
it("handles unitless lengths", function () {
484+
// As strings
485+
expect(utils.parseLength("0")).toBe(0);
486+
expect(utils.parseLength("1")).toBe(1);
487+
expect(utils.parseLength("10")).toBe(10);
488+
expect(utils.parseLength("100")).toBe(100);
489+
expect(utils.parseLength("1000.1")).toBe(1000);
490+
expect(utils.parseLength("1000.9")).toBe(1001);
491+
492+
// As numbers
493+
expect(utils.parseLength(0)).toBe(0);
494+
expect(utils.parseLength(1)).toBe(1);
495+
expect(utils.parseLength(10)).toBe(10);
496+
expect(utils.parseLength(100)).toBe(100);
497+
expect(utils.parseLength(1000.1)).toBe(1000);
498+
expect(utils.parseLength(1000.9)).toBe(1001);
499+
});
500+
483501
it("handles pixel lengths", function () {
484502
expect(utils.parseLength("10px")).toBe(10);
485503
expect(utils.parseLength("100px")).toBe(100);
@@ -881,3 +899,24 @@ describe("threshold_list ...", function () {
881899
]);
882900
});
883901
});
902+
903+
describe("is_option_truthy ...", function () {
904+
it("checks if an option is set", () => {
905+
// These options are considered truthy
906+
expect(utils.is_option_truthy("a")).toBe(true);
907+
expect(utils.is_option_truthy(true)).toBe(true);
908+
expect(utils.is_option_truthy("true")).toBe(true);
909+
expect(utils.is_option_truthy(1)).toBe(true);
910+
// Also "0" is considered truthy because it can be a valid option for
911+
// length, time and so forth.
912+
expect(utils.is_option_truthy(0)).toBe(true);
913+
914+
// These options are considered falsy
915+
expect(utils.is_option_truthy(undefined)).toBe(false);
916+
expect(utils.is_option_truthy(null)).toBe(false);
917+
expect(utils.is_option_truthy(false)).toBe(false);
918+
expect(utils.is_option_truthy("false")).toBe(false);
919+
expect(utils.is_option_truthy("none")).toBe(false);
920+
expect(utils.is_option_truthy("")).toBe(false);
921+
});
922+
});

src/pat/navigation/documentation.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ This would invoke a `click` event on the current navigation item and that can be
2626
The navigation pattern can be configured through a `data-pat-navigation` attribute.
2727
The available options are:
2828

29-
| Field | Default | Options | Description |
30-
| ------------------------ | -------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
31-
| item-wrapper | `li` | CSS selector | The DOM element which wraps each menu item. This is used to set the "current" and "in-path" classes also on the wrapper element. If empty, no wrapper element is used. |
32-
| in-path-class | `navigation-in-path` | CSS class name | Class name, which is set on item-wrapper elements if nested menu items are within the current path. |
33-
| current-class | `current` | CSS class name | Class name, which is set on item-wrapper or items if they are the currently selected menu option or - for hash links - when it's corresponding content item is in view. |
34-
| in-view-class | `in-view` | | CSS class for a navigation item when it's corresponding content item is in view. |
35-
| current-content-class | `current` | | CSS class for a content item when it is the current one. |
36-
| scroll-item-side | `top` | top, bottom, middle, auto | Side of element that scrolls. This is used to calculate the current item. The defined side of the element will be used to calculate the distance baseline. If this is set to "auto" then one of the "top" or "bottom" positions are used, depending on which one is nearer to the distance baseline. |
37-
| scroll-item-distance | `50%` | | Distance from side of scroll box. any amount in px or %. This is used to calculate the current item. The nearest element to the distance-baseline measured from the top of the container will get the current class. |
38-
| scroll-item-visibility | | none, most-visible | Visibility of the item/element in scroll box. This is used to calculate the current item. If "most-visible" is set, the element which is most visible in the scroll container gets the current class. If more elements have the same visibility ratio, the other conditions are used to calculate the current one. |
39-
| scroll-trigger-selector | `a[href^='#'].scroll-marker` | CSS selector, none | Selects the element within the pat-navigation container that should get a class current while scrolling, when applicable. |
29+
| Field | Default | Options | Description |
30+
| ----------------------- | ---------------------------- | ---------------------------------------- | ----------------------- |
31+
| item-wrapper | `li` | CSS selector | The DOM element which wraps each menu item. This is used to set the "current" and "in-path" classes also on the wrapper element. If empty, no wrapper element is used. |
32+
| in-path-class | `navigation-in-path` | CSS class name | Class name, which is set on item-wrapper elements if nested menu items are within the current path. |
33+
| current-class | `current` | CSS class name | Class name, which is set on item-wrapper or items if they are the currently selected menu option or - for hash links - when it's corresponding content item is in view. |
34+
| current-content-class | `current` | CSS class name | CSS class for a content item when it is the current one. |
35+
| in-view-class | `in-view` | CSS class name | CSS class for a navigation item when it's corresponding content item is in view. |
36+
| scrill-item-side | `top` | `top`, `bottom`, `middle`, `auto` | Side of element that scrolls. This is used to calculate the current item. The defined side of the element will be used to calculate the distance baseline. If this is set to "auto" then one of the "top" or "bottom" positions are used, depending on which one is nearer to the distance baseline. |
37+
| scrill-item-distance | `50%` | CSS length (px, %, vw, vh, vmin or vmax) | Distance from side of scroll box. any amount in px, %, vw, vh, vmin or vmax. This is used to calculate the current item. The nearest element to the distance-baseline measured from the top of the container will get the current class. |
38+
| scrill-item-visibility | | `none`, `most-visible` | Visibility of element in scroll box. This is used to calculate the current item. If "most-visible" is set, the element which is most visible in the scroll container gets the current class. If more elements have the same visibility ratio, the other conditions are used to calculate the current one. |
39+
| scroll-trigger-selector | `a[href^='#'].scroll-marker` | CSS selector, `none` | Selects the element within the pat-navigation container that should get a class current while scrolling, when applicable. |
4040

src/pat/navigation/navigation.js

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@ parser.addArgument("in-view-class", "in-view");
1717
parser.addArgument("current-class", "current");
1818
parser.addArgument("current-content-class", "navigation-current");
1919

20-
// Side of element that scrolls. top/bottom/middle/auto (default 'top')
21-
parser.addArgument("scroll-marker-side", "top", ["top", "bottom", "middle", "auto"]);
22-
// Distance from side of scroll box. any amount in px or % (default '50%')
23-
parser.addArgument("scroll-marker-distance", "50%");
24-
// Visibility of element in scroll box. most-visible or null (default null)
25-
parser.addArgument("scroll-marker-visibility", null, [null, "most-visible"]);
20+
parser.addArgument("scroll-item-side", "top", ["top", "bottom", "middle", "auto"]);
21+
parser.addArgument("scroll-item-distance", "50%");
22+
parser.addArgument("scroll-item-visibility", null, [null, "none", "most-visible"]);
23+
parser.addArgument("scroll-trigger-selector", "a[href^='#'].scroll-marker");
2624

2725
class Pattern extends BasePattern {
2826
static name = "navigation";
@@ -37,25 +35,28 @@ class Pattern extends BasePattern {
3735
this.init_listeners();
3836
this.init_markings();
3937

40-
this.scroll_marker = new ScrollMarker(this.el, {
41-
"current-class": this.options["current-class"],
42-
"current-content-class": this.options["current-content-class"],
43-
"in-view-class": this.options["in-view-class"],
44-
"side": this.options["scroll-marker-side"],
45-
"distance": this.options["scroll-marker-distance"],
46-
"visibility": this.options["scroll-marker-visibility"],
47-
});
38+
if (utils.is_option_truthy(this.options["scroll-trigger-selector"])) {
39+
this.scroll_marker = new ScrollMarker(this.el, {
40+
"current-class": this.options["current-class"],
41+
"current-content-class": this.options["current-content-class"],
42+
"in-view-class": this.options["in-view-class"],
43+
"side": this.options["scroll-item-side"],
44+
"distance": this.options["scroll-item-distance"],
45+
"visibility": this.options["scroll-item-visibility"],
46+
"selector": this.options["scroll-trigger-selector"],
47+
});
4848

49-
this.debounced_scroll_marker_enabler = utils.debounce(() => {
50-
log.debug("Enable scroll-marker.");
51-
this.scroll_marker.set_current_disabled = false;
52-
events.remove_event_listener(
53-
this.scroll_marker.scroll_container === window
54-
? document
55-
: this.scroll_marker.scroll_container,
56-
"pat-navigation__scroll_marker_enable"
57-
);
58-
}, 200);
49+
this.debounced_scroll_marker_enabler = utils.debounce(() => {
50+
log.debug("Enable scroll-marker.");
51+
this.scroll_marker.set_current_disabled = false;
52+
events.remove_event_listener(
53+
this.scroll_marker.scroll_container === window
54+
? document
55+
: this.scroll_marker.scroll_container,
56+
"pat-navigation__scroll_marker_enable"
57+
);
58+
}, 200);
59+
}
5960
}
6061

6162
/**
@@ -75,6 +76,12 @@ class Pattern extends BasePattern {
7576
// Mark the current item
7677
this.mark_current(ev.target);
7778

79+
if (
80+
!utils.is_option_truthy(this.options["scroll-trigger-selector"])
81+
) {
82+
// Don't do the scroll-marker stuff below, if it is disabled by the settings.
83+
return;
84+
}
7885
// Disable scroll marker to set the current class after
7986
// clicking in the menu and scrolling to the target.
8087
log.debug("Disable scroll-marker.");

src/pat/scroll-marker/documentation.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,13 @@ Here is a complete example:
4848

4949
### Options reference
5050

51-
| Property | Default Value | Values | Type | Description |
52-
| -------------- | ------------- | ------ | ----------------- | ----------------------------- |
53-
| in-view-class | in-view | | String | CSS class for a navigation item when it's corresponding content item is in view. |
54-
| current-class | current | | String | CSS class for a navigation item when it's corresponding content item is the current one. |
55-
| current-content-class | current | | String | CSS class for a content item when it is the current one. |
56-
| side | top | top, bottom, middle, auto | String | Side of element that scrolls. This is used to calculate the current item. The defined side of the element will be used to calculate the distance baseline. If this is set to "auto" then one of the "top" or "bottom" positions are used, depending on which one is nearer to the distance baseline. |
57-
| distance | 50% | | String | Distance from side of scroll box. any amount in px, %, vw, vh, vmin or vmax. This is used to calculate the current item. The nearest element to the distance-baseline measured from the top of the container will get the current class. |
58-
| visibility | | null, most-visible | String | Visibility of element in scroll box. This is used to calculate the current item. If "most-visible" is set, the element which is most visible in the scroll container gets the current class. If more elements have the same visibility ratio, the other conditions are used to calculate the current one. |
51+
| Property | Default | Options | Description |
52+
| --------------------- | -------------- | ---------------------------------------- | ----------------------------- |
53+
| current-class | `current` | CSS class name | CSS class for a navigation item when it's corresponding content item is the current one. |
54+
| current-content-class | `current` | CSS class name | CSS class for a content item when it is the current one. |
55+
| in-view-class | `in-view` | CSS class name | CSS class for a navigation item when it's corresponding content item is in view. |
56+
| side | `top` | `top`, `bottom`, `middle`, `auto` | Side of element that scrolls. This is used to calculate the current item. The defined side of the element will be used to calculate the distance baseline. If this is set to "auto" then one of the "top" or "bottom" positions are used, depending on which one is nearer to the distance baseline. |
57+
| distance | `50%` | CSS length (px, %, vw, vh, vmin or vmax) | Distance from side of scroll box. any amount in px, %, vw, vh, vmin or vmax. This is used to calculate the current item. The nearest element to the distance-baseline measured from the top of the container will get the current class. |
58+
| visibility | | `none`, `most-visible` | Visibility of element in scroll box. This is used to calculate the current item. If "most-visible" is set, the element which is most visible in the scroll container gets the current class. If more elements have the same visibility ratio, the other conditions are used to calculate the current one. |
59+
| selector | `a[href^='#']` | CSS selector, `none` | Selects the element within the pat-navigation container that should get a class current while scrolling, when applicable. |
60+

src/pat/scroll-marker/scroll-marker.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ parser.addArgument("in-view-class", "in-view");
1313
parser.addArgument("current-class", "current");
1414
parser.addArgument("current-content-class", "scroll-marker-current");
1515

16-
// Side of element that scrolls. top/bottom/middle/auto (default 'top')
1716
parser.addArgument("side", "top", ["top", "bottom", "middle", "auto"]);
18-
// Distance from side of scroll box. any amount in px, %, vw, vh, vmin or vmax (default '50%')
1917
parser.addArgument("distance", "50%");
20-
// Visibility of element in scroll box. most-visible or null (default null)
21-
parser.addArgument("visibility", null, [null, "most-visible"]);
18+
parser.addArgument("visibility", null, [null, "none", "most-visible"]);
19+
parser.addArgument("selector", "a[href^='#']");
2220

2321
class Pattern extends BasePattern {
2422
static name = "scroll-marker";
@@ -34,7 +32,7 @@ class Pattern extends BasePattern {
3432
init() {
3533
// Get all elements that are referenced by links in the current page.
3634
this.observables = new Map(
37-
[...dom.querySelectorAllAndMe(this.el, "a[href^='#']")]
35+
[...dom.querySelectorAllAndMe(this.el, this.options.selector)]
3836
.map(
3937
// Create the structure:
4038
// id: {link, target}

src/pat/scroll-marker/scroll-marker.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,30 @@ describe("pat-scroll-marker", () => {
232232
expect(id2.classList.contains("scroll-marker-current")).toBe(true);
233233
expect(id3.classList.contains("scroll-marker-current")).toBe(false);
234234
});
235+
236+
it("1.5: The selector option can exclude some items", async () => {
237+
// With the selector option you can include/exclude some items from
238+
// being included in the scroll-marker functionality.
239+
240+
const { nav_id1, nav_id2, nav_id3, id1, id2, id3 } =
241+
await create_scroll_marker({
242+
options: {
243+
selector: "a[href^='#']:not([href='#id3'])",
244+
},
245+
});
246+
247+
expect(nav_id1.classList.contains("in-view")).toBe(false);
248+
expect(nav_id2.classList.contains("in-view")).toBe(true);
249+
expect(nav_id3.classList.contains("in-view")).toBe(false);
250+
251+
expect(nav_id1.classList.contains("current")).toBe(false);
252+
expect(nav_id2.classList.contains("current")).toBe(true);
253+
expect(nav_id3.classList.contains("current")).toBe(false);
254+
255+
expect(id1.classList.contains("scroll-marker-current")).toBe(false);
256+
expect(id2.classList.contains("scroll-marker-current")).toBe(true);
257+
expect(id3.classList.contains("scroll-marker-current")).toBe(false);
258+
});
235259
});
236260

237261
describe("2: Test on element as scroll container", () => {

0 commit comments

Comments
 (0)