Skip to content

Commit 7b42573

Browse files
committed
Add containment support to shuffle
1 parent 17ff158 commit 7b42573

7 files changed

Lines changed: 134 additions & 7 deletions

File tree

.yarn/versions/5909970b.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
releases:
2+
"@pitter-patter/shuffle": patch

packages/docs/src/components/demos/shuffle.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,28 @@ img {
3030
.shuffle-drag-handle {
3131
color: #351900;
3232
}
33+
34+
.card-deck {
35+
display: flex;
36+
background-color: lightgray;
37+
gap: 1rem;
38+
border-radius: 0.25rem;
39+
justify-content: space-around;
40+
padding: 1rem;
41+
margin: 2rem;
42+
}
43+
44+
.card {
45+
border: 1px solid transparent;
46+
background-color: white;
47+
padding: 0.5rem;
48+
border-radius: 0.25rem;
49+
}
50+
51+
.card.shuffle-hover-block {
52+
border: 1px solid lightblue;
53+
}
54+
55+
.card[data-shuffle-clone] {
56+
background-color: white;
57+
}

packages/docs/src/components/demos/shuffle.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,37 @@ basic.spec.nodes = basic.spec.nodes.update("image", {
3434
inline: false,
3535
});
3636

37+
basic.spec.nodes = basic.spec.nodes.update("card_deck", {
38+
group: "block",
39+
content: "card+",
40+
toDOM() {
41+
return ["div", { "data-node-type": "card_deck" }, 0];
42+
},
43+
parseDOM: [
44+
{
45+
tag: 'div[data-node-type="card_deck"]',
46+
},
47+
],
48+
});
49+
50+
basic.spec.nodes = basic.spec.nodes.update("card", {
51+
content: "paragraph+",
52+
toDOM() {
53+
return ["div", { "data-node-type": "card" }, 0];
54+
},
55+
parseDOM: [
56+
{
57+
tag: 'div[data-node-type="card"]',
58+
},
59+
],
60+
pitterPatter: {
61+
shuffle: {
62+
containedBy: "card_deck",
63+
draggable: true,
64+
},
65+
},
66+
});
67+
3768
let nodes = basic.spec.nodes.update("paragraph", {
3869
...basic.spec.nodes.get("paragraph"),
3970
toDOM() {
@@ -63,6 +94,23 @@ const doc = schema.nodes.doc.create(null, [
6394
src: "https://t4.ftcdn.net/jpg/02/71/88/53/360_F_271885326_Jkc8UkWTYmgB3dJjhrot2QZEiLneCaaM.jpg",
6495
}),
6596
schema.nodes.paragraph.create(null, schema.text("Another paragraph not in a row.")),
97+
schema.nodes.card_deck.create({ shuffleStart: 2, shuffleEnd: 11 }, [
98+
schema.nodes.card.create(null, [
99+
schema.nodes.paragraph.create(null, [
100+
schema.text("You can configure containment on node types, too."),
101+
]),
102+
]),
103+
schema.nodes.card.create(null, [
104+
schema.nodes.paragraph.create(null, [
105+
schema.text("These cards can be dragged around within their deck…"),
106+
]),
107+
]),
108+
schema.nodes.card.create(null, [
109+
schema.nodes.paragraph.create(null, [
110+
schema.text("…but they can't be dropped outside of it!"),
111+
]),
112+
]),
113+
]),
66114
]);
67115

68116
function Image({ nodeProps, ref, children: _, ...props }: NodeViewComponentProps) {
@@ -81,6 +129,8 @@ function Image({ nodeProps, ref, children: _, ...props }: NodeViewComponentProps
81129

82130
const nodeViewComponents = {
83131
image: Image,
132+
card_deck: CardDeck,
133+
card: Card,
84134
};
85135

86136
function hoverDecorations(from: number, to: number) {
@@ -111,3 +161,24 @@ export function ShuffleDemo() {
111161
</ProseMirror>
112162
);
113163
}
164+
165+
function CardDeck({ nodeProps: _, ref, children, ...props }: NodeViewComponentProps) {
166+
return (
167+
<div
168+
ref={ref}
169+
{...props}
170+
className={`card-deck ${props.className ?? ""}`}
171+
data-node-type="card_deck"
172+
>
173+
{children}
174+
</div>
175+
);
176+
}
177+
178+
function Card({ nodeProps: _, ref, children, ...props }: NodeViewComponentProps) {
179+
return (
180+
<div ref={ref} {...props} className={`card ${props.className ?? ""}`} data-node-type="card">
181+
{children}
182+
</div>
183+
);
184+
}

packages/shuffle/src/plugin.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -423,11 +423,11 @@ export function startDragOnPointerDown(
423423
clone.style.transform = transform;
424424
clone.style.transformOrigin = transformOrigin;
425425

426-
setTimeout(() => {
427-
clone!.style.transition = "none";
428-
delete dom.dataset["shuffleActive"];
429-
clone!.remove();
430-
}, 250);
426+
// setTimeout(() => {
427+
// clone!.style.transition = "none";
428+
// delete dom.dataset["shuffleActive"];
429+
// clone!.remove();
430+
// }, 250);
431431

432432
return;
433433
}

packages/shuffle/src/schema.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Node, NodeSpec, NodeType, Schema } from "prosemirror-model";
1+
import { Node, NodeSpec, NodeType, ResolvedPos, Schema } from "prosemirror-model";
22

33
export const shuffleAttrs: NodeSpec["attrs"] = {
44
shuffleStart: {
@@ -137,6 +137,21 @@ export function getShuffleRowType(schema: Schema) {
137137
return rowType ?? null;
138138
}
139139

140+
export function getBeforeContainedBy($from: ResolvedPos) {
141+
const node = $from.nodeAfter;
142+
if (!node) return $from.doc.resolve(0);
143+
144+
const containedByName = node.type.spec.pitterPatter?.shuffle?.containedBy;
145+
if (!containedByName) return $from.doc.resolve(0);
146+
147+
let d = $from.depth;
148+
while ($from.node(d).type.name !== containedByName && d > 0) {
149+
d--;
150+
}
151+
152+
return $from.doc.resolve($from.before(d));
153+
}
154+
140155
declare module "prosemirror-model" {
141156
interface NodeSpec {
142157
pitterPatter?: PitterPatterSpec;

packages/shuffle/src/transform/autogroup.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export function autogroup(
1818
const node = view.state.doc.resolve(from).nodeAfter;
1919
if (!node) return null;
2020

21+
if (node.type.spec.pitterPatter?.shuffle?.containedBy) return null;
22+
2123
const adjacentElements = findAdjacentElements(clientY, view.dom.getBoundingClientRect()).filter(
2224
(el) => !el.dataset["shuffleActive"] && el !== view.dom,
2325
);

packages/shuffle/src/transform/reorder.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,33 @@ import { insertPoint } from "prosemirror-transform";
55
import { EditorView } from "prosemirror-view";
66

77
import { shufflePluginKey, ShufflePluginMeta } from "../plugin";
8+
import { getBeforeContainedBy } from "../schema.js";
89

910
export function reorder(
1011
view: EditorView,
1112
from: number,
1213
clientX: number,
1314
clientY: number,
1415
): Transaction | null {
15-
const node = view.state.doc.resolve(from).nodeAfter;
16+
const $from = view.state.doc.resolve(from);
17+
18+
const node = $from.nodeAfter;
1619
if (!node) return null;
1720

21+
const $containedBy = getBeforeContainedBy($from);
22+
1823
const posResult = view.posAtCoords({ left: clientX, top: clientY });
1924
if (!posResult) return null;
2025

2126
const { pos } = posResult;
2227

28+
if (
29+
pos <= $containedBy.pos ||
30+
pos >= $containedBy.pos + ($containedBy.nodeAfter ?? $containedBy.doc).nodeSize
31+
) {
32+
return null;
33+
}
34+
2335
const gap = findGap(view.state.doc, pos, node.type);
2436

2537
if (gap === null) return null;

0 commit comments

Comments
 (0)