Skip to content

Commit ac71af6

Browse files
authored
Merge pull request #17 from jagaapple/release/v0.1.1
# Changes and Fixes - Fix priority of dragging cursor style #16
2 parents 95af9a6 + 1532a30 commit ac71af6

File tree

9 files changed

+100
-32
lines changed

9 files changed

+100
-32
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
# Changelog
2+
## 0.1.1 (2020-03-21)
3+
- Fix priority of dragging cursor style #16 [@jagaapple](https://github.com/jagaapple)
4+
25
## 0.1.0 (2020-03-21)
36
- Initial public release - [@jagaapple](https://github.com/jagaapple)

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-sortful",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "Sortable components for horizontal and vertical, nested, and tree forms.",
55
"keywords": [
66
"React",

src/item/body.spec.ts

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,65 @@ import * as sinon from "sinon";
33
import { clearBodyStyle, setBodyStyle } from "./body";
44

55
describe("setBodyStyle", () => {
6+
let dummyDocumentElement: Document;
7+
let dummyElement: HTMLElement;
8+
let elementCreatorMock: sinon.SinonStub<Parameters<Document["createElement"]>, ReturnType<Document["createElement"]>>;
9+
let elementAttributeSetterSpy: sinon.SinonSpy<Parameters<Element["setAttribute"]>, ReturnType<Element["setAttribute"]>>;
10+
let childAppenderSpy: sinon.SinonSpy<Parameters<Node["appendChild"]>, ReturnType<Node["appendChild"]>>;
11+
12+
beforeEach(() => {
13+
elementAttributeSetterSpy = sinon.spy() as any;
14+
dummyElement = { setAttribute: elementAttributeSetterSpy, textContent: "" } as any;
15+
16+
elementCreatorMock = sinon.mock().returns(dummyElement) as any;
17+
childAppenderSpy = sinon.spy() as any;
18+
dummyDocumentElement = { createElement: elementCreatorMock, head: { appendChild: childAppenderSpy } } as any;
19+
});
20+
afterEach(() => {
21+
sinon.restore();
22+
});
23+
624
it('should set `style.userSelect` to "none"', () => {
725
const bodyElement = { style: {} } as HTMLElement;
826

9-
setBodyStyle(bodyElement, undefined);
27+
setBodyStyle(bodyElement, undefined, dummyDocumentElement);
1028
expect(bodyElement.style.userSelect).toBe("none");
1129
});
1230

31+
context("when `draggingCusrsorStyle` is undefined", () => {
32+
it("should not call `document.createElement`", () => {
33+
const bodyElement = { style: {} } as HTMLElement;
34+
35+
setBodyStyle(bodyElement, undefined, dummyDocumentElement);
36+
expect(elementCreatorMock.called).toBe(false);
37+
});
38+
39+
it("should not call `document.head.appendChild`", () => {
40+
const bodyElement = { style: {} } as HTMLElement;
41+
42+
setBodyStyle(bodyElement, undefined, dummyDocumentElement);
43+
expect(childAppenderSpy.called).toBe(false);
44+
});
45+
});
46+
1347
context("when `draggingCusrsorStyle` is not undefined", () => {
48+
const draggingCusrsorStyle = "dummy";
49+
1450
it('should set `style.userSelect` to "none"', () => {
1551
const bodyElement = { style: {} } as HTMLElement;
1652

17-
setBodyStyle(bodyElement, undefined);
53+
setBodyStyle(bodyElement, draggingCusrsorStyle, dummyDocumentElement);
1854
expect(bodyElement.style.userSelect).toBe("none");
1955
});
2056

21-
it("should set `style.cursor` to the string", () => {
57+
it("should create a style element, set attributes, and append to a document element", () => {
2258
const bodyElement = { style: {} } as HTMLElement;
23-
const draggingCusrsorStyle = "dummy";
2459

25-
setBodyStyle(bodyElement, draggingCusrsorStyle);
26-
expect(bodyElement.style.cursor).toBe(draggingCusrsorStyle);
60+
setBodyStyle(bodyElement, draggingCusrsorStyle, dummyDocumentElement);
61+
expect(elementCreatorMock.calledOnceWith("style")).toBe(true);
62+
expect(dummyElement.textContent).toBe(`* { cursor: ${draggingCusrsorStyle} !important; }`);
63+
expect(elementAttributeSetterSpy.calledOnceWith("id", "react-sortful-global-style")).toBe(true);
64+
expect(childAppenderSpy.calledOnceWith(dummyElement)).toBe(true);
2765
});
2866
});
2967
});
@@ -34,24 +72,46 @@ describe("clearBodyStyle", () => {
3472
Parameters<CSSStyleDeclaration["removeProperty"]>,
3573
ReturnType<CSSStyleDeclaration["removeProperty"]>
3674
>;
75+
let dummyDocumentElement: Document;
76+
let dummyElement: HTMLElement;
77+
let elementGetterMock: sinon.SinonStub<Parameters<Document["getElementById"]>, ReturnType<Document["getElementById"]>>;
78+
let elementRemoverSpy: sinon.SinonSpy<Parameters<Element["remove"]>, ReturnType<Element["remove"]>>;
3779

38-
beforeAll(() => {
80+
beforeEach(() => {
3981
propertyRemoverSpy = sinon.spy() as any;
4082
bodyElement = { style: { removeProperty: propertyRemoverSpy } } as any;
83+
84+
elementRemoverSpy = sinon.spy() as any;
85+
dummyElement = { remove: elementRemoverSpy } as any;
86+
87+
elementGetterMock = sinon.mock().returns(dummyElement) as any;
88+
dummyDocumentElement = { getElementById: elementGetterMock } as any;
4189
});
4290
afterEach(() => {
4391
sinon.restore();
4492
});
4593

4694
it('should call `style.removeProperty` with "user-select" of `bodyElement`', () => {
47-
clearBodyStyle(bodyElement);
95+
clearBodyStyle(bodyElement, dummyDocumentElement);
4896

4997
expect(propertyRemoverSpy.calledWith("user-select")).toBe(true);
5098
});
5199

52-
it('should call `style.removeProperty` with "cursor" of `bodyElement`', () => {
53-
clearBodyStyle(bodyElement);
100+
it("should remove a style element created by react-sortful", () => {
101+
clearBodyStyle(bodyElement, dummyDocumentElement);
102+
103+
expect(elementGetterMock.calledOnceWith("react-sortful-global-style")).toBe(true);
104+
expect(elementRemoverSpy.calledOnce).toBe(true);
105+
});
106+
107+
context("when `documentElement.getElementById` returns null", () => {
108+
beforeEach(() => {
109+
elementGetterMock = sinon.mock().returns(null) as any;
110+
dummyDocumentElement = { getElementById: elementGetterMock } as any;
111+
});
54112

55-
expect(propertyRemoverSpy.calledWith("cursor")).toBe(true);
113+
it("should not raise any errors", () => {
114+
expect(() => clearBodyStyle(bodyElement, dummyDocumentElement)).not.toThrowError();
115+
});
56116
});
57117
});

src/item/body.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
1-
export const setBodyStyle = (bodyElement: HTMLElement, draggingCusrsorStyle: string | undefined) => {
1+
const styleElementId = "react-sortful-global-style";
2+
3+
export const setBodyStyle = (
4+
bodyElement: HTMLElement,
5+
draggingCusrsorStyle: string | undefined,
6+
// istanbul ignore next
7+
documentElement = document,
8+
) => {
29
// Disables to select elements in entire page.
310
bodyElement.style.userSelect = "none";
411

512
// Applies a cursor style when dragging.
6-
if (draggingCusrsorStyle != undefined) bodyElement.style.cursor = draggingCusrsorStyle;
13+
if (draggingCusrsorStyle != undefined) {
14+
const styleElement = documentElement.createElement("style");
15+
styleElement.textContent = `* { cursor: ${draggingCusrsorStyle} !important; }`;
16+
styleElement.setAttribute("id", styleElementId);
17+
18+
documentElement.head.appendChild(styleElement);
19+
}
720
};
821

9-
export const clearBodyStyle = (bodyElement: HTMLElement) => {
22+
export const clearBodyStyle = (
23+
bodyElement: HTMLElement,
24+
// istanbul ignore next
25+
documentElement = document,
26+
) => {
1027
// Enables to select elements in entire page.
1128
bodyElement.style.removeProperty("user-select");
1229

1330
// Resets a cursor style when dragging.
14-
bodyElement.style.removeProperty("cursor");
31+
documentElement.getElementById(styleElementId)?.remove();
1532
};

stories/3-advanced-examples/custom-drag-handle.css.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
declare const styles: {
22
readonly "wrapper": string;
33
readonly "dropLine": string;
4+
readonly "dragHandle": string;
45
readonly "item": string;
56
readonly "placeholder": string;
6-
readonly "dragHandle": string;
77
readonly "ghost": string;
88
};
99
export = styles;

stories/3-advanced-examples/kanban.component.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,15 @@ const renderDropLineElement = (injectedProps: DropLineRendererInjectedProps) =>
6969

7070
export const KanbanComponent = () => {
7171
const [itemEntitiesMapState, setItemEntitiesMapState] = React.useState(initialItemEntitiesMap);
72-
const [isDraggingAnyItemState, setIsDraggingAnyItemState] = React.useState(false);
7372

7473
const createItemElements = React.useCallback(
7574
(items: DummyItem[]) =>
7675
items.map((item, index) => (
7776
<Item key={item.id} identifier={item.id} index={index}>
78-
<div className={classnames(styles.item, { [styles.draggingAny]: isDraggingAnyItemState })}>{item.title}</div>
77+
<div className={styles.item}>{item.title}</div>
7978
</Item>
8079
)),
81-
[isDraggingAnyItemState],
80+
[],
8281
);
8382
const leftItemElements = React.useMemo(() => {
8483
const leftItems = itemEntitiesMapState.get(leftRootItemId)!.children!.map((itemId) => itemEntitiesMapState.get(itemId)!);
@@ -130,12 +129,7 @@ export const KanbanComponent = () => {
130129
[],
131130
);
132131

133-
const onDragStart = React.useCallback(() => {
134-
setIsDraggingAnyItemState(true);
135-
}, []);
136132
const onDragEnd = React.useCallback((meta: DragEndMeta<DummyItem["id"]>) => {
137-
setIsDraggingAnyItemState(false);
138-
139133
if (meta.groupIdentifier === meta.nextGroupIdentifier && meta.index === meta.nextIndex) return;
140134

141135
const newMap = new Map(itemEntitiesMapState.entries());
@@ -179,7 +173,6 @@ export const KanbanComponent = () => {
179173
renderStackedGroup={renderStackedGroupElement}
180174
draggingCursorStyle="grabbing"
181175
itemSpacing={10}
182-
onDragStart={onDragStart}
183176
onDragEnd={onDragEnd}
184177
>
185178
<Item identifier={leftRootItem.id} index={0} isLocked isGroup isLonely>

stories/3-advanced-examples/kanban.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,6 @@
5555
.item.placeholder {
5656
color: white;
5757
background-color: #cfdce5;
58-
cursor: grabbing;
59-
}
60-
.item.draggingAny {
61-
cursor: grabbing;
6258
}
6359

6460
.ghost {

stories/3-advanced-examples/kanban.css.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ declare const styles: {
77
readonly "items": string;
88
readonly "item": string;
99
readonly "placeholder": string;
10-
readonly "draggingAny": string;
1110
readonly "ghost": string;
1211
};
1312
export = styles;

0 commit comments

Comments
 (0)