-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from RealA10N/dragables
Added the Grabzone & Grabable components
- Loading branch information
Showing
7 changed files
with
256 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,29 @@ | ||
<script lang="ts"> | ||
import { flip } from 'svelte/animate'; | ||
import { blur } from 'svelte/transition'; | ||
<script lang="ts" context="module"> | ||
import type { BoxState as GenericBoxState, Stringable } from '$lib/interfaces/strings'; | ||
</script> | ||
|
||
<script lang="ts" generics="Text extends Stringable"> | ||
import StringBox from '$lib/strings/StringBox.svelte'; | ||
import type { BoxState } from '$lib/interfaces/strings'; | ||
export let array: BoxState[]; | ||
import Grabzone from '$lib/grabs/Grabzone.svelte'; | ||
type BoxState = GenericBoxState<Text>; | ||
export let items: BoxState[]; | ||
export let onGrab: (item: BoxState) => any = () => {}; | ||
export let onUpdate: (item: BoxState) => any = () => {}; | ||
export let onRelease: (item: BoxState) => any = () => {}; | ||
</script> | ||
|
||
{#each array as state (state.text)} | ||
<div | ||
class="inline-block" | ||
animate:flip={{ duration: 400 }} | ||
in:blur={{ duration: 400 }} | ||
out:blur={{ duration: 150 }} | ||
> | ||
<StringBox {state} /> | ||
<Grabzone | ||
{items} | ||
let:item | ||
let:dummy | ||
flipOptions={{ duration: 400 }} | ||
{onGrab} | ||
{onRelease} | ||
{onUpdate} | ||
> | ||
<div class="inline-block" class:opacity-50={dummy}> | ||
<StringBox state={item} /> | ||
</div> | ||
{/each} | ||
</Grabzone> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<script lang="ts"> | ||
import { onMount } from 'svelte'; | ||
import type { P } from './utils'; | ||
let grabbingPointer: number | undefined; | ||
$: grabbed = grabbingPointer !== undefined; | ||
let dummyPosition: P, grabOffset: P; | ||
const grab = (e: PointerEvent) => { | ||
grabbingPointer = e.pointerId; | ||
grabOffset = { x: e.offsetX, y: e.offsetY }; | ||
updateDummy(e); | ||
}; | ||
const release = (e: PointerEvent) => { | ||
if (e.pointerId === grabbingPointer) grabbingPointer = undefined; | ||
}; | ||
const move = (e: PointerEvent) => { | ||
if (e.pointerId === grabbingPointer) updateDummy(e); | ||
}; | ||
const updateDummy = (e: PointerEvent) => | ||
(dummyPosition = { x: e.clientX - grabOffset.x, y: e.clientY - grabOffset.y }); | ||
onMount(() => { | ||
window.addEventListener('pointerup', release); | ||
window.addEventListener('pointermove', move); | ||
return () => { | ||
window.removeEventListener('pointerup', release); | ||
window.removeEventListener('pointermove', move); | ||
}; | ||
}); | ||
</script> | ||
|
||
<span class="inline-block select-none cursor-grab touch-none" on:pointerdown={grab}> | ||
<slot dummy={grabbed} /> | ||
</span> | ||
|
||
{#if grabbed} | ||
<span | ||
class="inline-block select-none fixed cursor-grabbing" | ||
style="top: {dummyPosition.y}px; left: {dummyPosition.x}px" | ||
> | ||
<slot name="floating" dummy={false} /> | ||
</span> | ||
{/if} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<script context="module" lang="ts"> | ||
import type { Id, Identifiable } from './utils'; | ||
</script> | ||
|
||
<script lang="ts" generics="Item extends Identifiable"> | ||
import { onMount } from 'svelte'; | ||
import { flip, type FlipParams } from 'svelte/animate'; | ||
import { findClosestIdx, elementPos, type P, moveByIdx, findIdx } from './utils'; | ||
export let flipOptions: FlipParams = {}; | ||
export let items: Item[]; | ||
let grabbedItem: Item | undefined; | ||
let elements = [] as HTMLElement[]; | ||
let elementsPos = [] as P[]; | ||
$: isGrabbed = grabbedItem !== undefined; | ||
let grabbingPointerId: number = -1; | ||
let grabPos: P, grabOffset: P; | ||
// callbacks for user of compoennt | ||
export let onGrab: (item: Item) => any = () => {}; | ||
export let onUpdate: (item: Item) => any = () => {}; | ||
export let onRelease: (item: Item) => any = () => {}; | ||
const grab = (e: PointerEvent, item: Item) => { | ||
if (isGrabbed) return; | ||
grabbedItem = item; | ||
grabbingPointerId = e.pointerId; | ||
grabOffset = { x: e.offsetX, y: e.offsetY }; | ||
saveElementsPositions(); | ||
updateFloatingPos(e); | ||
updateItemsPositions(); | ||
onGrab(item); | ||
}; | ||
const drag = (e: PointerEvent) => { | ||
if (!isEventRelevent(e)) return; | ||
updateFloatingPos(e); | ||
updateItemsPositions(); | ||
}; | ||
const release = (e: PointerEvent) => { | ||
if (!isEventRelevent(e)) return; | ||
onRelease(grabbedItem!); | ||
grabbedItem = undefined; | ||
}; | ||
const isEventRelevent = (e: PointerEvent) => isGrabbed && e.pointerId === grabbingPointerId; | ||
const updateFloatingPos = (e: PointerEvent) => | ||
(grabPos = { x: e.clientX - grabOffset.x, y: e.clientY - grabOffset.y }); | ||
const updateItemsPositions = () => { | ||
const closestIdx = findClosestIdx(grabPos, elementsPos)!; | ||
const grabbedIdx = findIdx(items, grabbedItem!.id); | ||
if (closestIdx === grabbedIdx) return; | ||
moveByIdx(items, grabbedIdx, closestIdx); | ||
items = items; | ||
onUpdate(grabbedItem!); | ||
}; | ||
const saveElementsPositions = () => { | ||
for (const [idx, elem] of elements.entries()) elementsPos[idx] = elementPos(elem); | ||
}; | ||
onMount(() => { | ||
window.addEventListener('pointerup', release); | ||
window.addEventListener('pointermove', drag); | ||
return () => { | ||
window.removeEventListener('pointerup', release); | ||
window.removeEventListener('pointermove', drag); | ||
}; | ||
}); | ||
</script> | ||
|
||
{#each items as item, idx (item.id)} | ||
<span animate:flip={flipOptions} on:pointerdown={(e) => grab(e, item)} bind:this={elements[idx]}> | ||
<slot {item} dummy={item === grabbedItem} /> | ||
</span> | ||
{/each} | ||
|
||
{#if grabbedItem !== undefined} | ||
{#key items} | ||
<span class="grabbed fixed" style="left: {grabPos.x}px; top: {grabPos.y}px"> | ||
<slot item={grabbedItem} dummy={false} /> | ||
</span> | ||
{/key} | ||
{/if} | ||
|
||
<style lang="postcss"> | ||
span:not(.grabbed) { | ||
@apply inline-block select-none touch-none cursor-grab; | ||
} | ||
span.grabbed { | ||
@apply cursor-grabbing; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
export type Id = string; | ||
export interface Identifiable { | ||
id: Id; | ||
} | ||
|
||
export interface P { | ||
x: number; | ||
y: number; | ||
} | ||
|
||
const abs = Math.abs; | ||
const dx = (a: P, b: P) => abs(a.x - b.x); | ||
const dy = (a: P, b: P) => abs(a.y - b.y); | ||
|
||
export const dist = (a: P, b: P): number => dx(a, b) + dy(a, b); | ||
|
||
export const elementPos = (e: HTMLElement): P => e.getBoundingClientRect() as P; | ||
|
||
export const distElements = (a: HTMLElement, b: HTMLElement) => dist(elementPos(a), elementPos(b)); | ||
|
||
export const findClosestIdx = (p: P, pnts: P[]): number | undefined => { | ||
let d = Infinity, | ||
best: number | undefined = undefined; | ||
for (const [idx, o] of pnts.entries()) { | ||
const dt = dist(p, o); | ||
if (dt < d) (d = dt), (best = idx); | ||
} | ||
return best; | ||
}; | ||
|
||
export const findIdx = <Item extends Identifiable>(arr: Item[], id: Id) => | ||
arr.findIndex((item) => item.id === id); | ||
|
||
export const moveByIdx = <T>(arr: T[], src: number, trg: number) => | ||
arr.splice(trg, 0, arr.splice(src, 1)[0]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,16 @@ | ||
export type Text = string | number | bigint; | ||
import type { Identifiable } from '$lib/grabs/utils'; | ||
|
||
export interface BoxState { | ||
export type Stringable = string | number | bigint; | ||
|
||
export interface BoxState<Text extends Stringable> extends Identifiable { | ||
text: Text; | ||
highlight?: boolean; | ||
color?: 'red' | 'green' | 'yellow'; | ||
} | ||
|
||
export interface StringMatchingState { | ||
text: BoxState[]; | ||
pattern: BoxState[]; | ||
text: BoxState<string>[]; | ||
pattern: BoxState<string>[]; | ||
shift: number; // how much to shift the pattern (index in text) | ||
focus: number; // index of pattern to be at the center of the view | ||
} |