stateMarker
Add data attributes to track drag state
The stateMarker
plugin adds data attributes that track drag state. These attributes enable CSS styling based on drag status and provide hooks for debugging and analytics.
stateMarker(); // Adds data-neodrag-state and data-neodrag-count attributes
Attributes added:
data-neodrag=""
- Marks element as draggabledata-neodrag-state="idle|dragging"
- Current drag statedata-neodrag-count="0"
- Number of completed drags
Note: StateMarker is included by default and is non-cancelable, so it always runs regardless of other plugin cancellations.
Basic Usage
import { useRef } from 'react';
import { stateMarker, useDraggable } from '@neodrag/react';
function StateMarkerExample() {
const elementRef = useRef<HTMLDivElement>(null);
useDraggable(elementRef, [stateMarker()]);
return (
<div ref={elementRef}>
Drag me - watch the data attributes change
</div>
);
}
Reading State Attributes
import { useRef, useState } from 'react';
import { stateMarker, events, useDraggable } from '@neodrag/react';
function StateReader() {
const elementRef = useRef<HTMLDivElement>(null);
const [currentState, setCurrentState] = useState('idle');
const [dragCount, setDragCount] = useState(0);
const updateStateInfo = () => {
if (elementRef.current) {
const state =
elementRef.current.getAttribute('data-neodrag-state') ||
'idle';
const count = parseInt(
elementRef.current.getAttribute('data-neodrag-count') || '0',
);
setCurrentState(state);
setDragCount(count);
}
};
useDraggable(elementRef, [
stateMarker(),
events({
onDragStart: updateStateInfo,
onDrag: updateStateInfo,
onDragEnd: updateStateInfo,
}),
]);
return (
<div ref={elementRef}>
State: {currentState}
<br />
Drag Count: {dragCount}
</div>
);
}
Monitoring State Changes
import { useRef, useState } from 'react';
import { stateMarker, events, useDraggable } from '@neodrag/react';
interface Notification {
id: number;
message: string;
timestamp: string;
}
function StateMonitor() {
const ref = useRef<HTMLDivElement>(null);
const [notifications, setNotifications] = useState<Notification[]>(
[],
);
const addNotification = (message: string) => {
const notification = {
id: Date.now(),
message,
timestamp: new Date().toLocaleTimeString(),
};
setNotifications((prev) => [...prev, notification]);
// Remove after 3 seconds
setTimeout(() => {
setNotifications((prev) =>
prev.filter((n) => n.id !== notification.id),
);
}, 3000);
};
useDraggable(ref, [
stateMarker(),
events({
onDragStart: () => addNotification('Drag started'),
onDragEnd: (data) => {
const count = data.rootNode.getAttribute(
'data-neodrag-count',
);
addNotification(`Drag ended (Total: ${count})`);
},
}),
]);
return (
<div>
<div ref={ref}>Drag me for notifications</div>
<div className="notifications">
{notifications.map((notification) => (
<div key={notification.id}>
{notification.timestamp}: {notification.message}
</div>
))}
</div>
</div>
);
}
Analytics Integration
import { useRef } from 'react';
import { stateMarker, events, useDraggable } from '@neodrag/react';
function AnalyticsExample() {
const ref = useRef<HTMLDivElement>(null);
const trackDragAnalytics = (eventName: string, data: any) => {
// Send to analytics service
analytics.track(eventName, {
elementId: data.rootNode.id,
dragCount: data.rootNode.getAttribute('data-neodrag-count'),
position: data.offset,
timestamp: Date.now(),
});
};
useDraggable(ref, [
stateMarker(),
events({
onDragStart: (data) => trackDragAnalytics('drag_started', data),
onDragEnd: (data) => trackDragAnalytics('drag_completed', data),
}),
]);
return <div ref={ref}>Analytics-tracked element</div>;
}
Debug Inspector
import { useRef, useState } from 'react';
import { stateMarker, events, useDraggable } from '@neodrag/react';
interface DebugInfo {
state: string;
count: number;
isDragging: boolean;
lastEventTime: string | null;
}
function DebugInspector() {
const ref = useRef<HTMLDivElement>(null);
const [debugInfo, setDebugInfo] = useState<DebugInfo>({
state: 'idle',
count: 0,
isDragging: false,
lastEventTime: null,
});
const updateDebugInfo = (eventType: string, data: any) => {
setDebugInfo({
state:
data.rootNode.getAttribute('data-neodrag-state') || 'idle',
count: parseInt(
data.rootNode.getAttribute('data-neodrag-count') || '0',
),
isDragging: eventType === 'drag' || eventType === 'dragStart',
lastEventTime: new Date().toLocaleTimeString(),
});
};
useDraggable(ref, [
stateMarker(),
events({
onDragStart: (data) => updateDebugInfo('dragStart', data),
onDrag: (data) => updateDebugInfo('drag', data),
onDragEnd: (data) => updateDebugInfo('dragEnd', data),
}),
]);
return (
<div>
<div ref={ref}>Drag me to see debug info</div>
<div className="debug-panel">
<h3>Debug Information</h3>
<p>State: {debugInfo.state}</p>
<p>Total Drags: {debugInfo.count}</p>
<p>
Currently Dragging: {debugInfo.isDragging ? 'Yes' : 'No'}
</p>
<p>Last Event: {debugInfo.lastEventTime || 'None'}</p>
</div>
</div>
);
}
How It Works
The stateMarker plugin manages data attributes through the drag lifecycle:
-
Setup phase:
- Adds
data-neodrag=""
to mark element - Sets
data-neodrag-state="idle"
- Initializes
data-neodrag-count="0"
- Adds
-
Start phase:
- Updates
data-neodrag-state="dragging"
- Updates
-
End phase:
- Resets
data-neodrag-state="idle"
- Increments
data-neodrag-count
- Resets
-
Non-cancelable:
- Always runs regardless of other plugin cancellations
- Ensures consistent state tracking
// Simplified internal implementation
setup(ctx) {
setNodeDataset(ctx.rootNode, 'neodrag', '');
setNodeDataset(ctx.rootNode, 'neodrag-state', 'idle');
setNodeDataset(ctx.rootNode, 'neodrag-count', '0');
},
start(ctx) {
setNodeDataset(ctx.rootNode, 'neodrag-state', 'dragging');
},
end(ctx, state) {
setNodeDataset(ctx.rootNode, 'neodrag-state', 'idle');
setNodeDataset(ctx.rootNode, 'neodrag-count', ++state.count);
}
API Reference
function stateMarker(): Plugin;
Parameters: None
Data attributes added:
data-neodrag=""
- Marks element as draggabledata-neodrag-state="idle|dragging"
- Current drag statedata-neodrag-count="0"
- Number of completed drags
Behavior:
- Non-cancelable - always runs
- Updates attributes automatically during drag lifecycle
- Increments count only on successful drag completion
- Provides hooks for CSS styling and JavaScript integration
Returns: A plugin object for use with draggable.