Skip to content

Commit 220ad6d

Browse files
committed
add brushToDelete interaction
1 parent d412b3a commit 220ad6d

File tree

3 files changed

+121
-5
lines changed

3 files changed

+121
-5
lines changed

packages/labs/src/folk-toolbar.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { css, property, ReactiveElement, state, type PropertyValues } from '@folkjs/dom/ReactiveElement';
2-
import { brushInkShape } from './interactions/brush';
2+
import { brushInkShape, brushToDeleteElements } from './interactions/brush';
33
import { clickToCreateArrow, clickToCreateEventPropagator } from './interactions/connection';
44
import { dragToCreateShape } from './interactions/create-element';
5-
import { deleteElementByClick } from './interactions/delete';
65

76
export type Interaction = (container: HTMLElement, cancellationSignal: AbortSignal) => Promise<void>;
87

@@ -16,7 +15,9 @@ export class FolkInstrument extends ReactiveElement {
1615
[
1716
'erase',
1817
async (container, cancellationSignal) => {
19-
await deleteElementByClick(container, cancellationSignal);
18+
await brushToDeleteElements(container, cancellationSignal, (el) =>
19+
el.shape !== undefined && el.parentElement === container ? el : null,
20+
);
2021
},
2122
],
2223
[

packages/labs/src/interactions/brush.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,119 @@ export function brushInkShape(container: HTMLElement, cancellationSignal: AbortS
9191
container.addEventListener('touchmove', onTouch, { capture: true });
9292
});
9393
}
94+
95+
const styles = new CSSStyleSheet();
96+
styles.replaceSync(`
97+
html:has([folk-selected-element]) * {
98+
cursor: crosshair;
99+
}
100+
101+
[folk-selected-element] {
102+
outline: solid 1px blue !important;
103+
outline-offset: -1px;
104+
}
105+
`);
106+
107+
export function brushToSelectElements<T extends Element = Element>(
108+
container: HTMLElement,
109+
cancellationSignal: AbortSignal,
110+
filter?: (el: Element) => T | null,
111+
) {
112+
return new Promise<T[]>((resolve) => {
113+
const elements = new Set<T>();
114+
115+
function elementsToSelect(event: PointerEvent) {
116+
document.elementsFromPoint(event.pageX, event.pageY).forEach((el) => {
117+
if (filter === undefined) {
118+
el.setAttribute('folk-selected-element', '');
119+
elements.add(el as T);
120+
return;
121+
}
122+
123+
const filteredEl = filter(el);
124+
if (filteredEl) {
125+
elements.add(el as T);
126+
el.setAttribute('folk-selected-element', '');
127+
}
128+
});
129+
}
130+
131+
function onPointerDown(event: PointerEvent) {
132+
event.preventDefault();
133+
event.stopImmediatePropagation();
134+
event.stopPropagation();
135+
container.setPointerCapture(event.pointerId);
136+
137+
container.addEventListener('pointermove', onPointerMove, { capture: true });
138+
container.addEventListener('pointerup', onPointerUp, { capture: true });
139+
elementsToSelect(event);
140+
}
141+
142+
function onPointerMove(event: PointerEvent) {
143+
event.preventDefault();
144+
event.stopImmediatePropagation();
145+
event.stopPropagation();
146+
elementsToSelect(event);
147+
}
148+
149+
function onPointerUp(event: PointerEvent) {
150+
event.preventDefault();
151+
event.stopImmediatePropagation();
152+
event.stopPropagation();
153+
154+
cleanUp();
155+
resolve(Array.from(elements));
156+
}
157+
158+
function onTouch(event: TouchEvent) {
159+
event.preventDefault();
160+
event.stopImmediatePropagation();
161+
event.stopPropagation();
162+
}
163+
164+
function onCancel() {
165+
cleanUp();
166+
resolve([]);
167+
}
168+
169+
function cleanUp() {
170+
elements.forEach((el) => el.removeAttribute('folk-selected-element'));
171+
cancellationSignal.removeEventListener('abort', onCancel);
172+
container.removeEventListener('touchmove', onTouch, { capture: true });
173+
container.removeEventListener('pointerdown', onPointerDown, { capture: true });
174+
container.removeEventListener('pointermove', onPointerMove, { capture: true });
175+
container.removeEventListener('pointerup', onPointerUp, { capture: true });
176+
container.ownerDocument.adoptedStyleSheets.splice(container.ownerDocument.adoptedStyleSheets.indexOf(styles), 1);
177+
}
178+
179+
cancellationSignal.addEventListener('abort', onCancel);
180+
container.addEventListener('pointerdown', onPointerDown, { capture: true });
181+
container.addEventListener('touchmove', onTouch, { capture: true });
182+
container.ownerDocument.adoptedStyleSheets.push(styles);
183+
});
184+
}
185+
186+
const deleteStyles = new CSSStyleSheet();
187+
styles.replaceSync(`
188+
[folk-selected-element] {
189+
outline: none;
190+
opacity: 0.4;
191+
}
192+
`);
193+
194+
export async function brushToDeleteElements<T extends Element = Element>(
195+
container: HTMLElement,
196+
cancellationSignal: AbortSignal,
197+
filter?: (el: Element) => T | null,
198+
) {
199+
container.ownerDocument.adoptedStyleSheets.push(deleteStyles);
200+
const elements = await brushToSelectElements<T>(container, cancellationSignal, filter);
201+
container.ownerDocument.adoptedStyleSheets.splice(
202+
container.ownerDocument.adoptedStyleSheets.indexOf(deleteStyles),
203+
1,
204+
);
205+
206+
elements.forEach((el) => el.remove());
207+
208+
return elements;
209+
}

website/canvas/index.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@
3232
}
3333

3434
div[folk-shape] {
35-
border: solid 2px black;
35+
border: solid 5px black;
3636
border-radius: 13px;
37-
background: green;
3837
}
3938
</style>
4039
</head>

0 commit comments

Comments
 (0)