@reshaped/utilities 3.9.1-canary.3 → 3.10.0-canary.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +232 -0
- package/dist/a11y/Chain.d.ts +20 -0
- package/dist/a11y/Chain.js +60 -0
- package/dist/a11y/TrapFocus.d.ts +28 -0
- package/dist/a11y/TrapFocus.js +162 -0
- package/dist/a11y/TrapScreenReader.d.ts +15 -0
- package/dist/a11y/TrapScreenReader.js +42 -0
- package/dist/a11y/focus.d.ts +38 -0
- package/dist/a11y/focus.js +101 -0
- package/dist/a11y/index.d.ts +4 -0
- package/dist/a11y/index.js +3 -0
- package/dist/a11y/keyboardMode.d.ts +4 -0
- package/dist/a11y/keyboardMode.js +10 -0
- package/dist/a11y/tests/Chain.test.js +88 -0
- package/dist/a11y/tests/TrapFocus.test.d.ts +1 -0
- package/dist/a11y/tests/TrapFocus.test.js +313 -0
- package/dist/a11y/tests/TrapScreenReader.test.d.ts +1 -0
- package/dist/a11y/tests/TrapScreenReader.test.js +126 -0
- package/dist/a11y/tests/focus.test.d.ts +1 -0
- package/dist/a11y/tests/focus.test.js +278 -0
- package/dist/a11y/tests/keyboardMode.test.d.ts +1 -0
- package/dist/a11y/tests/keyboardMode.test.js +27 -0
- package/dist/a11y/types.d.ts +24 -0
- package/dist/a11y/types.js +1 -0
- package/dist/constants/keys.d.ts +11 -0
- package/dist/constants/keys.js +11 -0
- package/dist/css/StyleCache.d.ts +7 -0
- package/dist/css/StyleCache.js +19 -0
- package/dist/css/classNames.d.ts +7 -0
- package/dist/css/classNames.js +19 -0
- package/dist/css/index.d.ts +2 -0
- package/dist/css/index.js +4 -0
- package/dist/css/tests/StyleCache.test.d.ts +1 -0
- package/dist/css/tests/StyleCache.test.js +45 -0
- package/dist/css/tests/classNames.test.d.ts +1 -0
- package/dist/css/tests/classNames.test.js +63 -0
- package/dist/dom/findClosestScrollableContainer.d.ts +5 -0
- package/dist/dom/findClosestScrollableContainer.js +12 -0
- package/dist/dom/findParent.d.ts +2 -0
- package/dist/dom/findParent.js +10 -0
- package/dist/dom/index.d.ts +3 -0
- package/dist/dom/index.js +4 -0
- package/dist/dom/tests/findClosestScrollableContainer.test.d.ts +1 -0
- package/dist/dom/tests/findClosestScrollableContainer.test.js +61 -0
- package/dist/dom/tests/findParent.test.d.ts +1 -0
- package/dist/dom/tests/findParent.test.js +45 -0
- package/dist/flyout/Flyout.d.ts +2 -2
- package/dist/flyout/Flyout.js +6 -6
- package/dist/flyout/index.d.ts +1 -1
- package/dist/flyout/index.js +1 -1
- package/dist/flyout/tests/Flyout.test.js +9 -9
- package/dist/flyout/types.d.ts +2 -2
- package/dist/flyout/utilities/applyPosition.js +1 -1
- package/dist/flyout/utilities/tests/applyPosition.test.js +10 -10
- package/dist/flyout/utilities/tests/calculateLayoutAdjustment.test.js +2 -2
- package/dist/flyout/utilities/tests/calculatePosition.test.js +1 -1
- package/dist/flyout/utilities/tests/centerBySize.test.js +1 -1
- package/dist/flyout/utilities/tests/getPositionFallbacks.test.js +1 -1
- package/dist/flyout/utilities/tests/getRTLPosition.test.js +1 -1
- package/dist/flyout/utilities/tests/isFullyVisible.test.js +1 -1
- package/dist/helpers/classNames.d.ts +7 -0
- package/dist/helpers/classNames.js +19 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +2 -0
- package/dist/helpers/tests/classNames.test.d.ts +1 -0
- package/dist/helpers/tests/classNames.test.js +63 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/internal.d.ts +11 -0
- package/dist/internal.js +10 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +16 -0
- package/dist/scroll/disable.d.ts +7 -0
- package/dist/scroll/disable.js +15 -0
- package/dist/scroll/helpers.d.ts +1 -0
- package/dist/scroll/helpers.js +17 -0
- package/dist/scroll/index.d.ts +2 -0
- package/dist/scroll/index.js +4 -0
- package/dist/scroll/lock.d.ts +7 -0
- package/dist/scroll/lock.js +26 -0
- package/dist/scroll/lockSafari.d.ts +2 -0
- package/dist/scroll/lockSafari.js +20 -0
- package/dist/scroll/lockStandard.d.ts +4 -0
- package/dist/scroll/lockStandard.js +15 -0
- package/dist/scroll/tests/lock.test.d.ts +1 -0
- package/dist/scroll/tests/lock.test.js +81 -0
- package/package.json +6 -1
- package/dist/flyout/utilities/findClosestFixedContainer.d.ts +0 -5
- package/dist/flyout/utilities/findClosestFixedContainer.js +0 -18
- package/dist/flyout/utilities/tests/findClosestFixedContainer.test.js +0 -46
- /package/dist/{flyout/utilities/tests/findClosestFixedContainer.test.d.ts → a11y/tests/Chain.test.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { expect, test, describe } from "vitest";
|
|
2
|
+
import findParent from "../findParent.js";
|
|
3
|
+
describe("dom/findParent", () => {
|
|
4
|
+
test("returns null when element has no parent", () => {
|
|
5
|
+
const element = document.createElement("div");
|
|
6
|
+
const result = findParent(element, () => true);
|
|
7
|
+
expect(result).toBe(null);
|
|
8
|
+
});
|
|
9
|
+
test("returns null when no parent matches the condition", () => {
|
|
10
|
+
const parent = document.createElement("div");
|
|
11
|
+
const child = document.createElement("span");
|
|
12
|
+
parent.appendChild(child);
|
|
13
|
+
const result = findParent(child, () => false);
|
|
14
|
+
expect(result).toBe(null);
|
|
15
|
+
});
|
|
16
|
+
test("returns the immediate parent when it matches", () => {
|
|
17
|
+
const parent = document.createElement("div");
|
|
18
|
+
parent.className = "target";
|
|
19
|
+
const child = document.createElement("span");
|
|
20
|
+
parent.appendChild(child);
|
|
21
|
+
const result = findParent(child, (el) => el.className === "target");
|
|
22
|
+
expect(result).toBe(parent);
|
|
23
|
+
});
|
|
24
|
+
test("returns a distant ancestor when it matches", () => {
|
|
25
|
+
const grandparent = document.createElement("div");
|
|
26
|
+
grandparent.className = "target";
|
|
27
|
+
const parent = document.createElement("div");
|
|
28
|
+
const child = document.createElement("span");
|
|
29
|
+
grandparent.appendChild(parent);
|
|
30
|
+
parent.appendChild(child);
|
|
31
|
+
const result = findParent(child, (el) => el.className === "target");
|
|
32
|
+
expect(result).toBe(grandparent);
|
|
33
|
+
});
|
|
34
|
+
test("returns the first matching parent", () => {
|
|
35
|
+
const grandparent = document.createElement("div");
|
|
36
|
+
grandparent.className = "match";
|
|
37
|
+
const parent = document.createElement("div");
|
|
38
|
+
parent.className = "match";
|
|
39
|
+
const child = document.createElement("span");
|
|
40
|
+
grandparent.appendChild(parent);
|
|
41
|
+
parent.appendChild(child);
|
|
42
|
+
const result = findParent(child, (el) => el.className === "match");
|
|
43
|
+
expect(result).toBe(parent);
|
|
44
|
+
});
|
|
45
|
+
});
|
package/dist/flyout/Flyout.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ declare class Flyout {
|
|
|
7
7
|
* Public methods
|
|
8
8
|
*/
|
|
9
9
|
update: (options?: Partial<Options>) => ReturnType<typeof applyPosition>;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
activate: () => ReturnType<typeof this.update>;
|
|
11
|
+
deactivate: () => void;
|
|
12
12
|
}
|
|
13
13
|
export default Flyout;
|
package/dist/flyout/Flyout.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { findClosestScrollableContainer } from "../dom/index.js";
|
|
2
|
+
import { rafThrottle } from "../helpers/index.js";
|
|
2
3
|
import applyPosition from "./utilities/applyPosition.js";
|
|
3
|
-
import findClosestScrollableContainer from "./utilities/findClosestScrollableContainer.js";
|
|
4
4
|
class Flyout {
|
|
5
5
|
#active = false;
|
|
6
6
|
#lastUsedPosition = null;
|
|
@@ -18,7 +18,7 @@ class Flyout {
|
|
|
18
18
|
return result;
|
|
19
19
|
};
|
|
20
20
|
#addParentScrollHandler = () => {
|
|
21
|
-
const { trigger,
|
|
21
|
+
const { trigger, onDeactivate } = this.#options;
|
|
22
22
|
if (!trigger)
|
|
23
23
|
return;
|
|
24
24
|
const container = findClosestScrollableContainer({ el: trigger });
|
|
@@ -36,7 +36,7 @@ class Flyout {
|
|
|
36
36
|
triggerBounds.left < containerBounds.left ||
|
|
37
37
|
triggerBounds.right > containerBounds.right ||
|
|
38
38
|
triggerBounds.bottom > containerBounds.bottom) {
|
|
39
|
-
|
|
39
|
+
onDeactivate();
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
42
|
this.#update();
|
|
@@ -86,7 +86,7 @@ class Flyout {
|
|
|
86
86
|
this.#options = { ...this.#options, ...options };
|
|
87
87
|
return this.#update();
|
|
88
88
|
};
|
|
89
|
-
|
|
89
|
+
activate = () => {
|
|
90
90
|
const result = this.#update();
|
|
91
91
|
this.#addParentScrollHandler();
|
|
92
92
|
this.#addRTLHandler();
|
|
@@ -94,7 +94,7 @@ class Flyout {
|
|
|
94
94
|
this.#active = true;
|
|
95
95
|
return result;
|
|
96
96
|
};
|
|
97
|
-
|
|
97
|
+
deactivate = () => {
|
|
98
98
|
this.#lastUsedPosition = null;
|
|
99
99
|
this.#active = false;
|
|
100
100
|
this.#removeHandlers();
|
package/dist/flyout/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from "./Flyout";
|
|
1
|
+
export { default as Flyout } from "./Flyout";
|
package/dist/flyout/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from "./Flyout.js";
|
|
1
|
+
export { default as Flyout } from "./Flyout.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test, describe, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import Flyout from "
|
|
2
|
+
import Flyout from "flyout/Flyout";
|
|
3
3
|
describe("flyout/Flyout", () => {
|
|
4
4
|
let content;
|
|
5
5
|
let trigger;
|
|
@@ -31,9 +31,9 @@ describe("flyout/Flyout", () => {
|
|
|
31
31
|
trigger,
|
|
32
32
|
triggerCoordinates: null,
|
|
33
33
|
position: "top",
|
|
34
|
-
onClose,
|
|
34
|
+
onDeactivate: onClose,
|
|
35
35
|
});
|
|
36
|
-
const result = flyout.
|
|
36
|
+
const result = flyout.activate();
|
|
37
37
|
expect(result.position).toBe("top");
|
|
38
38
|
trigger.style.top = "0px";
|
|
39
39
|
const resultUpdate = flyout.update();
|
|
@@ -53,9 +53,9 @@ describe("flyout/Flyout", () => {
|
|
|
53
53
|
trigger,
|
|
54
54
|
triggerCoordinates: null,
|
|
55
55
|
position: "top",
|
|
56
|
-
onClose,
|
|
56
|
+
onDeactivate: onClose,
|
|
57
57
|
});
|
|
58
|
-
flyout.
|
|
58
|
+
flyout.activate();
|
|
59
59
|
// Scroll trigger out of bounds
|
|
60
60
|
trigger.style.position = "absolute";
|
|
61
61
|
trigger.style.top = "-100px";
|
|
@@ -88,9 +88,9 @@ describe("flyout/Flyout", () => {
|
|
|
88
88
|
trigger,
|
|
89
89
|
triggerCoordinates: null,
|
|
90
90
|
position: "top",
|
|
91
|
-
onClose,
|
|
91
|
+
onDeactivate: onClose,
|
|
92
92
|
});
|
|
93
|
-
flyout.
|
|
93
|
+
flyout.activate();
|
|
94
94
|
// Scroll within bounds
|
|
95
95
|
container.scrollTop = 50;
|
|
96
96
|
container.dispatchEvent(new Event("scroll", { bubbles: true }));
|
|
@@ -111,9 +111,9 @@ describe("flyout/Flyout", () => {
|
|
|
111
111
|
trigger,
|
|
112
112
|
triggerCoordinates: null,
|
|
113
113
|
position: "start",
|
|
114
|
-
onClose,
|
|
114
|
+
onDeactivate: onClose,
|
|
115
115
|
});
|
|
116
|
-
const initialResult = flyout.
|
|
116
|
+
const initialResult = flyout.activate();
|
|
117
117
|
expect(initialResult.position).toBe("start");
|
|
118
118
|
// Start is positioned from the right side
|
|
119
119
|
expect(content.style.right).toBe("0px");
|
package/dist/flyout/types.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type Options = {
|
|
|
11
11
|
content: HTMLElement;
|
|
12
12
|
trigger?: HTMLElement | null;
|
|
13
13
|
container?: HTMLElement | null;
|
|
14
|
-
triggerCoordinates
|
|
14
|
+
triggerCoordinates?: Coordinates | null;
|
|
15
15
|
position: Position;
|
|
16
16
|
fallbackPositions?: Position[];
|
|
17
17
|
width?: Width;
|
|
@@ -19,6 +19,6 @@ export type Options = {
|
|
|
19
19
|
fallbackMinHeight?: string;
|
|
20
20
|
contentGap?: number;
|
|
21
21
|
contentShift?: number;
|
|
22
|
-
|
|
22
|
+
onDeactivate: () => void;
|
|
23
23
|
};
|
|
24
24
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import getShadowRoot from "../../dom/
|
|
1
|
+
import { getShadowRoot } from "../../dom/index.js";
|
|
2
2
|
import isRTL from "../../i18n/isRTL.js";
|
|
3
3
|
import { VIEWPORT_OFFSET, RESET_STYLES } from "../constants.js";
|
|
4
4
|
import calculateLayoutAdjustment from "./calculateLayoutAdjustment.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test, describe, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import applyPosition from "
|
|
2
|
+
import applyPosition from "flyout/utilities/applyPosition";
|
|
3
3
|
describe("flyout/applyPosition", () => {
|
|
4
4
|
let content;
|
|
5
5
|
let trigger;
|
|
@@ -31,7 +31,7 @@ describe("flyout/applyPosition", () => {
|
|
|
31
31
|
triggerCoordinates: null,
|
|
32
32
|
position: "top",
|
|
33
33
|
lastUsedPosition: null,
|
|
34
|
-
|
|
34
|
+
onDeactivate: vi.fn(),
|
|
35
35
|
});
|
|
36
36
|
expect(result.position).toBe("top");
|
|
37
37
|
});
|
|
@@ -44,7 +44,7 @@ describe("flyout/applyPosition", () => {
|
|
|
44
44
|
triggerCoordinates: null,
|
|
45
45
|
position: "top",
|
|
46
46
|
lastUsedPosition: null,
|
|
47
|
-
|
|
47
|
+
onDeactivate: vi.fn(),
|
|
48
48
|
});
|
|
49
49
|
expect(result.position).toBe("bottom");
|
|
50
50
|
});
|
|
@@ -58,7 +58,7 @@ describe("flyout/applyPosition", () => {
|
|
|
58
58
|
triggerCoordinates,
|
|
59
59
|
position: "top",
|
|
60
60
|
lastUsedPosition: null,
|
|
61
|
-
|
|
61
|
+
onDeactivate: vi.fn(),
|
|
62
62
|
});
|
|
63
63
|
expect(result.position).toBe("top");
|
|
64
64
|
expect(content.style.transform).toBe(`translate(150px, ${250 - window.innerHeight}px)`);
|
|
@@ -70,7 +70,7 @@ describe("flyout/applyPosition", () => {
|
|
|
70
70
|
triggerCoordinates: null,
|
|
71
71
|
position: "top",
|
|
72
72
|
lastUsedPosition: null,
|
|
73
|
-
|
|
73
|
+
onDeactivate: vi.fn(),
|
|
74
74
|
});
|
|
75
75
|
}).toThrow("Trigger bounds are required");
|
|
76
76
|
});
|
|
@@ -82,7 +82,7 @@ describe("flyout/applyPosition", () => {
|
|
|
82
82
|
position: "top",
|
|
83
83
|
width: "200px",
|
|
84
84
|
lastUsedPosition: null,
|
|
85
|
-
|
|
85
|
+
onDeactivate: vi.fn(),
|
|
86
86
|
});
|
|
87
87
|
expect(content.style.width).toBe("200px");
|
|
88
88
|
});
|
|
@@ -94,7 +94,7 @@ describe("flyout/applyPosition", () => {
|
|
|
94
94
|
position: "top",
|
|
95
95
|
width: "trigger",
|
|
96
96
|
lastUsedPosition: null,
|
|
97
|
-
|
|
97
|
+
onDeactivate: vi.fn(),
|
|
98
98
|
});
|
|
99
99
|
expect(result.position).toBeTruthy();
|
|
100
100
|
});
|
|
@@ -108,7 +108,7 @@ describe("flyout/applyPosition", () => {
|
|
|
108
108
|
triggerCoordinates: null,
|
|
109
109
|
position: "top",
|
|
110
110
|
lastUsedPosition: null,
|
|
111
|
-
|
|
111
|
+
onDeactivate: vi.fn(),
|
|
112
112
|
});
|
|
113
113
|
// Should try full-width positions
|
|
114
114
|
expect(result.position).toBeTruthy();
|
|
@@ -123,7 +123,7 @@ describe("flyout/applyPosition", () => {
|
|
|
123
123
|
position: "top",
|
|
124
124
|
fallbackPositions: ["start"],
|
|
125
125
|
lastUsedPosition: null,
|
|
126
|
-
|
|
126
|
+
onDeactivate: vi.fn(),
|
|
127
127
|
});
|
|
128
128
|
expect(result.position).toBe("start");
|
|
129
129
|
});
|
|
@@ -135,7 +135,7 @@ describe("flyout/applyPosition", () => {
|
|
|
135
135
|
triggerCoordinates: null,
|
|
136
136
|
position: "top",
|
|
137
137
|
lastUsedPosition: null,
|
|
138
|
-
|
|
138
|
+
onDeactivate: vi.fn(),
|
|
139
139
|
});
|
|
140
140
|
// Clone should be removed
|
|
141
141
|
expect(document.body.children.length).toBe(initialChildCount);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expect, test, describe, vi, beforeEach } from "vitest";
|
|
2
|
-
import { VIEWPORT_OFFSET } from "
|
|
3
|
-
import calculateLayoutAdjustment from "
|
|
2
|
+
import { VIEWPORT_OFFSET } from "flyout/constants";
|
|
3
|
+
import calculateLayoutAdjustment from "flyout/utilities/calculateLayoutAdjustment";
|
|
4
4
|
describe("flyout/calculateLayoutAdjustment", () => {
|
|
5
5
|
const createBounds = (left, top, width, height) => {
|
|
6
6
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test, describe, vi, beforeEach } from "vitest";
|
|
2
|
-
import calculatePosition from "
|
|
2
|
+
import calculatePosition from "flyout/utilities/calculatePosition";
|
|
3
3
|
describe("flyout/calculatePosition", () => {
|
|
4
4
|
const createBounds = (left, top, width, height) => {
|
|
5
5
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test, describe } from "vitest";
|
|
2
|
-
import centerBySize from "
|
|
2
|
+
import centerBySize from "flyout/utilities/centerBySize";
|
|
3
3
|
describe("flyout/centerBySize", () => {
|
|
4
4
|
test("centers even value", () => {
|
|
5
5
|
expect(centerBySize(100, 50)).toEqual(25);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test, describe } from "vitest";
|
|
2
|
-
import getPositionFallbacks from "
|
|
2
|
+
import getPositionFallbacks from "flyout/utilities/getPositionFallbacks";
|
|
3
3
|
describe("flyout/getPositionFallbacks", () => {
|
|
4
4
|
test("returns original position first for top-start", () => {
|
|
5
5
|
const result = getPositionFallbacks("top-start");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test, describe } from "vitest";
|
|
2
|
-
import getRTLPosition from "
|
|
2
|
+
import getRTLPosition from "flyout/utilities/getRTLPosition";
|
|
3
3
|
describe("flyout/getRTLPosition", () => {
|
|
4
4
|
test("keeps top position", () => {
|
|
5
5
|
expect(getRTLPosition("top")).toEqual("top");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import isFullyVisible from "flyout/utilities/isFullyVisible";
|
|
1
2
|
import { expect, test, describe } from "vitest";
|
|
2
|
-
import isFullyVisible from "../isFullyVisible.js";
|
|
3
3
|
describe("flyout/isFullyVisible", () => {
|
|
4
4
|
test("returns true when flyout is fully visible within visual container", () => {
|
|
5
5
|
const result = isFullyVisible({
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type ClassNameValue = string | null | undefined | false;
|
|
2
|
+
export type ClassName = ClassNameValue | ClassNameValue[] | ClassName[];
|
|
3
|
+
/**
|
|
4
|
+
* Resolve an array of values into a classname string
|
|
5
|
+
*/
|
|
6
|
+
declare const classNames: (...args: ClassName[]) => string;
|
|
7
|
+
export default classNames;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve an array of values into a classname string
|
|
3
|
+
*/
|
|
4
|
+
const classNames = (...args) => {
|
|
5
|
+
return args
|
|
6
|
+
.reduce((acc, cur) => {
|
|
7
|
+
if (Array.isArray(cur)) {
|
|
8
|
+
const className = classNames(...cur);
|
|
9
|
+
if (!className)
|
|
10
|
+
return acc;
|
|
11
|
+
return `${acc} ${className}`;
|
|
12
|
+
}
|
|
13
|
+
if (cur)
|
|
14
|
+
return `${acc} ${cur}`;
|
|
15
|
+
return acc;
|
|
16
|
+
}, "")
|
|
17
|
+
.trim();
|
|
18
|
+
};
|
|
19
|
+
export default classNames;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as rafThrottle } from "./rafThrottle";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { expect, test, describe } from "vitest";
|
|
2
|
+
import classNames from "../classNames.js";
|
|
3
|
+
describe("helpers/classNames", () => {
|
|
4
|
+
test("returns empty string when no arguments are provided", () => {
|
|
5
|
+
expect(classNames()).toBe("");
|
|
6
|
+
});
|
|
7
|
+
test("returns a single class name", () => {
|
|
8
|
+
expect(classNames("foo")).toBe("foo");
|
|
9
|
+
});
|
|
10
|
+
test("concatenates multiple class names", () => {
|
|
11
|
+
expect(classNames("foo", "bar", "baz")).toBe("foo bar baz");
|
|
12
|
+
});
|
|
13
|
+
test("filters out null values", () => {
|
|
14
|
+
expect(classNames("foo", null, "bar")).toBe("foo bar");
|
|
15
|
+
expect(classNames(null, "foo", null)).toBe("foo");
|
|
16
|
+
expect(classNames(null, null)).toBe("");
|
|
17
|
+
});
|
|
18
|
+
test("filters out undefined values", () => {
|
|
19
|
+
expect(classNames("foo", undefined, "bar")).toBe("foo bar");
|
|
20
|
+
expect(classNames(undefined, "foo", undefined)).toBe("foo");
|
|
21
|
+
expect(classNames(undefined, undefined)).toBe("");
|
|
22
|
+
});
|
|
23
|
+
test("filters out false values", () => {
|
|
24
|
+
expect(classNames("foo", false, "bar")).toBe("foo bar");
|
|
25
|
+
expect(classNames(false, "foo", false)).toBe("foo");
|
|
26
|
+
expect(classNames(false, false)).toBe("");
|
|
27
|
+
});
|
|
28
|
+
test("handles arrays of class names", () => {
|
|
29
|
+
expect(classNames(["foo", "bar"])).toBe("foo bar");
|
|
30
|
+
expect(classNames(["foo", "bar"], "baz")).toBe("foo bar baz");
|
|
31
|
+
expect(classNames("foo", ["bar", "baz"])).toBe("foo bar baz");
|
|
32
|
+
});
|
|
33
|
+
test("handles nested arrays", () => {
|
|
34
|
+
expect(classNames(["foo", ["bar", "baz"]])).toBe("foo bar baz");
|
|
35
|
+
expect(classNames([["foo", "bar"], "baz"])).toBe("foo bar baz");
|
|
36
|
+
expect(classNames([["foo"], ["bar"], ["baz"]])).toBe("foo bar baz");
|
|
37
|
+
});
|
|
38
|
+
test("filters out falsy values in arrays", () => {
|
|
39
|
+
expect(classNames(["foo", null, "bar"])).toBe("foo bar");
|
|
40
|
+
expect(classNames(["foo", undefined, "bar"])).toBe("foo bar");
|
|
41
|
+
expect(classNames(["foo", false, "bar"])).toBe("foo bar");
|
|
42
|
+
expect(classNames([null, undefined, false])).toBe("");
|
|
43
|
+
});
|
|
44
|
+
test("handles deeply nested arrays with falsy values", () => {
|
|
45
|
+
expect(classNames([["foo", null], [undefined, "bar"], false])).toBe("foo bar");
|
|
46
|
+
expect(classNames(["foo", [null, undefined, ["bar", false, "baz"]]])).toBe("foo bar baz");
|
|
47
|
+
});
|
|
48
|
+
test("handles empty arrays", () => {
|
|
49
|
+
expect(classNames([])).toBe("");
|
|
50
|
+
expect(classNames("foo", [], "bar")).toBe("foo bar");
|
|
51
|
+
expect(classNames([[], []])).toBe("");
|
|
52
|
+
});
|
|
53
|
+
test("handles empty strings", () => {
|
|
54
|
+
expect(classNames("", "foo")).toBe("foo");
|
|
55
|
+
expect(classNames("foo", "", "bar")).toBe("foo bar");
|
|
56
|
+
expect(classNames("", "", "")).toBe("");
|
|
57
|
+
});
|
|
58
|
+
test("handles conditional class names", () => {
|
|
59
|
+
const isActive = true;
|
|
60
|
+
const isDisabled = false;
|
|
61
|
+
expect(classNames("button", isActive && "active", isDisabled && "disabled")).toBe("button active");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as isRTL } from "./isRTL";
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { Flyout } from "./flyout/index.js";
|
|
2
|
+
export { TrapFocus } from "./a11y/index.js";
|
|
3
|
+
export { lockScroll } from "./scroll/index.js";
|
|
4
|
+
export { isRTL } from "./i18n/index.js";
|
|
5
|
+
export { classNames } from "./css/index.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal utilities re-used in other Reshaped components but not meant to be used as a public API
|
|
3
|
+
* Their API is subject to change without a major version bump.
|
|
4
|
+
*
|
|
5
|
+
* If you want to use one of these utilities, open an issue or a PR about moving it to the public API file
|
|
6
|
+
*/
|
|
7
|
+
export { focusableSelector, getActiveElement, getFocusableElements, focusNextElement, focusPreviousElement, focusFirstElement, focusLastElement, activateKeyboardMode, deactivateKeyboardMode, checkKeyboardMode, } from "./a11y";
|
|
8
|
+
export type { TrapMode, FocusableElement } from "./a11y";
|
|
9
|
+
export { disableScroll, enableScroll } from "./scroll";
|
|
10
|
+
export { rafThrottle } from "./helpers";
|
|
11
|
+
export { getShadowRoot, findParent } from "./dom";
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal utilities re-used in other Reshaped components but not meant to be used as a public API
|
|
3
|
+
* Their API is subject to change without a major version bump.
|
|
4
|
+
*
|
|
5
|
+
* If you want to use one of these utilities, open an issue or a PR about moving it to the public API file
|
|
6
|
+
*/
|
|
7
|
+
export { focusableSelector, getActiveElement, getFocusableElements, focusNextElement, focusPreviousElement, focusFirstElement, focusLastElement, activateKeyboardMode, deactivateKeyboardMode, checkKeyboardMode, } from "./a11y/index.js";
|
|
8
|
+
export { disableScroll, enableScroll } from "./scroll/index.js";
|
|
9
|
+
export { rafThrottle } from "./helpers/index.js";
|
|
10
|
+
export { getShadowRoot, findParent } from "./dom/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isIOS: () => boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const testPlatform = (re) => {
|
|
2
|
+
// Using experimental and deprecated features here since that's the only way to detect platform consistently
|
|
3
|
+
const platform =
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
window.navigator.userAgentData?.platform || window.navigator.platform;
|
|
7
|
+
return typeof window !== "undefined" ? re.test(platform) : false;
|
|
8
|
+
};
|
|
9
|
+
const isIPhone = () => testPlatform(/^iPhone/i);
|
|
10
|
+
const isMac = () => testPlatform(/^Mac/i);
|
|
11
|
+
const isIPad = () => {
|
|
12
|
+
return (testPlatform(/^iPad/i) ||
|
|
13
|
+
// iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
|
|
14
|
+
(isMac() && navigator.maxTouchPoints > 1));
|
|
15
|
+
};
|
|
16
|
+
export const isIOS = () => isIPhone() || isIPad();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const preventDefault: (e: Event) => void;
|
|
2
|
+
/**
|
|
3
|
+
* Prevent scrolling events for the cases when dragging elements
|
|
4
|
+
* without locking the page with overflow
|
|
5
|
+
*/
|
|
6
|
+
export declare const disableScroll: () => void;
|
|
7
|
+
export declare const enableScroll: () => void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const preventDefault = (e) => e.preventDefault();
|
|
2
|
+
/**
|
|
3
|
+
* Prevent scrolling events for the cases when dragging elements
|
|
4
|
+
* without locking the page with overflow
|
|
5
|
+
*/
|
|
6
|
+
export const disableScroll = () => {
|
|
7
|
+
window.addEventListener("wheel", preventDefault, { passive: false });
|
|
8
|
+
window.addEventListener("touchmove", preventDefault, { passive: false });
|
|
9
|
+
document.body.style.userSelect = "none";
|
|
10
|
+
};
|
|
11
|
+
export const enableScroll = () => {
|
|
12
|
+
window.removeEventListener("wheel", preventDefault);
|
|
13
|
+
window.removeEventListener("touchmove", preventDefault);
|
|
14
|
+
document.body.style.userSelect = "";
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getScrollbarWidth: () => number;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const getScrollbarWidth = (() => {
|
|
2
|
+
let scrollbarWidth;
|
|
3
|
+
return () => {
|
|
4
|
+
if (scrollbarWidth)
|
|
5
|
+
return scrollbarWidth;
|
|
6
|
+
const scrollDiv = document.createElement("div");
|
|
7
|
+
scrollDiv.style.position = "absolute";
|
|
8
|
+
scrollDiv.style.top = "-9999px";
|
|
9
|
+
scrollDiv.style.width = "50px";
|
|
10
|
+
scrollDiv.style.height = "50px";
|
|
11
|
+
scrollDiv.style.overflow = "scroll";
|
|
12
|
+
document.body.appendChild(scrollDiv);
|
|
13
|
+
scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth;
|
|
14
|
+
document.body.removeChild(scrollDiv);
|
|
15
|
+
return scrollbarWidth;
|
|
16
|
+
};
|
|
17
|
+
})();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { findClosestScrollableContainer } from "../dom/index.js";
|
|
2
|
+
import { isIOS } from "../platform/index.js";
|
|
3
|
+
import lockSafariScroll from "./lockSafari.js";
|
|
4
|
+
import lockStandardScroll from "./lockStandard.js";
|
|
5
|
+
export const lockScroll = (args) => {
|
|
6
|
+
const isIOSLock = isIOS();
|
|
7
|
+
let reset = () => { };
|
|
8
|
+
const container = args?.containerEl ??
|
|
9
|
+
(args?.originEl && findClosestScrollableContainer({ el: args.originEl })) ??
|
|
10
|
+
document.body;
|
|
11
|
+
const lockedBodyScroll = container === document.body;
|
|
12
|
+
// Already locked so no need to lock again and trigger the callback
|
|
13
|
+
if (container.style.overflow === "hidden")
|
|
14
|
+
return;
|
|
15
|
+
if (isIOSLock && lockedBodyScroll) {
|
|
16
|
+
reset = lockSafariScroll();
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
reset = lockStandardScroll({ container });
|
|
20
|
+
}
|
|
21
|
+
args?.callback?.();
|
|
22
|
+
return (args) => {
|
|
23
|
+
reset();
|
|
24
|
+
args?.callback?.();
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StyleCache } from "../css/index.js";
|
|
2
|
+
const styleCache = new StyleCache();
|
|
3
|
+
const lockSafariScroll = () => {
|
|
4
|
+
const viewport = window.visualViewport;
|
|
5
|
+
const offsetLeft = viewport?.offsetLeft || 0;
|
|
6
|
+
const offsetTop = viewport?.offsetTop || 0;
|
|
7
|
+
const { scrollX, scrollY } = window;
|
|
8
|
+
styleCache.set(document.body, {
|
|
9
|
+
position: "fixed",
|
|
10
|
+
top: `${-(scrollY - Math.floor(offsetTop))}px`,
|
|
11
|
+
left: `${-(scrollX - Math.floor(offsetLeft))}px`,
|
|
12
|
+
right: "0",
|
|
13
|
+
overflow: "hidden",
|
|
14
|
+
});
|
|
15
|
+
return () => {
|
|
16
|
+
styleCache.reset();
|
|
17
|
+
window.scrollTo({ top: scrollY, left: scrollX, behavior: "instant" });
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export default lockSafariScroll;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StyleCache } from "../css/index.js";
|
|
2
|
+
import { getScrollbarWidth } from "./helpers.js";
|
|
3
|
+
const styleCache = new StyleCache();
|
|
4
|
+
const lockStandardScroll = (args) => {
|
|
5
|
+
const { container } = args;
|
|
6
|
+
const rect = container.getBoundingClientRect();
|
|
7
|
+
const isOverflowing = rect.left + rect.right < window.innerWidth;
|
|
8
|
+
styleCache.set(container, { overflow: "hidden" });
|
|
9
|
+
if (isOverflowing) {
|
|
10
|
+
const scrollBarWidth = getScrollbarWidth();
|
|
11
|
+
styleCache.set(container, { paddingRight: `${scrollBarWidth}px` });
|
|
12
|
+
}
|
|
13
|
+
return () => styleCache.reset();
|
|
14
|
+
};
|
|
15
|
+
export default lockStandardScroll;
|