450 lines
11 KiB
JavaScript
450 lines
11 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Generate dnd-kit Configuration
|
|
* Creates configuration for sensors, modifiers, and animations
|
|
*/
|
|
|
|
// Generate sensor configuration
|
|
function generateSensorConfig(options = {}) {
|
|
const {
|
|
enablePointer = true,
|
|
enableKeyboard = true,
|
|
enableTouch = true,
|
|
pointerActivationConstraint = { distance: 10 },
|
|
keyboardCoordinateGetter = 'sortableKeyboardCoordinates',
|
|
touchActivationConstraint = { delay: 250, tolerance: 5 },
|
|
} = options;
|
|
|
|
const sensors = [];
|
|
|
|
if (enablePointer) {
|
|
sensors.push({
|
|
sensor: 'PointerSensor',
|
|
options: {
|
|
activationConstraint: pointerActivationConstraint,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (enableKeyboard) {
|
|
sensors.push({
|
|
sensor: 'KeyboardSensor',
|
|
options: {
|
|
coordinateGetter: keyboardCoordinateGetter,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (enableTouch) {
|
|
sensors.push({
|
|
sensor: 'TouchSensor',
|
|
options: {
|
|
activationConstraint: touchActivationConstraint,
|
|
},
|
|
});
|
|
}
|
|
|
|
return sensors;
|
|
}
|
|
|
|
// Generate modifier configuration
|
|
function generateModifierConfig(options = {}) {
|
|
const {
|
|
restrictToVerticalAxis = false,
|
|
restrictToHorizontalAxis = false,
|
|
restrictToWindowEdges = true,
|
|
restrictToParentElement = false,
|
|
snapToGrid = false,
|
|
gridSize = 20,
|
|
} = options;
|
|
|
|
const modifiers = [];
|
|
|
|
if (restrictToVerticalAxis) {
|
|
modifiers.push({
|
|
modifier: 'restrictToVerticalAxis',
|
|
});
|
|
}
|
|
|
|
if (restrictToHorizontalAxis) {
|
|
modifiers.push({
|
|
modifier: 'restrictToHorizontalAxis',
|
|
});
|
|
}
|
|
|
|
if (restrictToWindowEdges) {
|
|
modifiers.push({
|
|
modifier: 'restrictToWindowEdges',
|
|
});
|
|
}
|
|
|
|
if (restrictToParentElement) {
|
|
modifiers.push({
|
|
modifier: 'restrictToParentElement',
|
|
});
|
|
}
|
|
|
|
if (snapToGrid) {
|
|
modifiers.push({
|
|
modifier: 'snapCenterToCursor',
|
|
options: { gridSize },
|
|
});
|
|
}
|
|
|
|
return modifiers;
|
|
}
|
|
|
|
// Generate animation configuration
|
|
function generateAnimationConfig(options = {}) {
|
|
const {
|
|
duration = 200,
|
|
easing = 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
dragOverlay = true,
|
|
dropAnimation = true,
|
|
layoutAnimation = true,
|
|
sideEffects = {
|
|
opacity: 0.5,
|
|
scale: 1.05,
|
|
},
|
|
} = options;
|
|
|
|
return {
|
|
duration,
|
|
easing,
|
|
dragOverlay: dragOverlay ? {
|
|
opacity: sideEffects.opacity,
|
|
scale: sideEffects.scale,
|
|
} : null,
|
|
dropAnimation: dropAnimation ? {
|
|
duration: duration * 1.5,
|
|
easing,
|
|
keyframes: [
|
|
{ transform: 'scale(1.05)' },
|
|
{ transform: 'scale(0.95)' },
|
|
{ transform: 'scale(1)' },
|
|
],
|
|
} : null,
|
|
layoutAnimation: layoutAnimation ? {
|
|
duration,
|
|
easing,
|
|
} : null,
|
|
};
|
|
}
|
|
|
|
// Generate collision detection configuration
|
|
function generateCollisionConfig(strategy = 'closestCenter') {
|
|
const strategies = {
|
|
closestCenter: {
|
|
name: 'closestCenter',
|
|
description: 'Drop on the element whose center is closest to the pointer',
|
|
},
|
|
closestCorners: {
|
|
name: 'closestCorners',
|
|
description: 'Drop on the element whose corners are closest',
|
|
},
|
|
rectIntersection: {
|
|
name: 'rectIntersection',
|
|
description: 'Drop on the element with the largest intersection area',
|
|
},
|
|
pointerWithin: {
|
|
name: 'pointerWithin',
|
|
description: 'Drop on the element the pointer is within',
|
|
},
|
|
};
|
|
|
|
return strategies[strategy] || strategies.closestCenter;
|
|
}
|
|
|
|
// Generate sorting strategy configuration
|
|
function generateSortingStrategy(type = 'vertical') {
|
|
const strategies = {
|
|
vertical: {
|
|
name: 'verticalListSortingStrategy',
|
|
description: 'For vertical lists',
|
|
direction: 'vertical',
|
|
},
|
|
horizontal: {
|
|
name: 'horizontalListSortingStrategy',
|
|
description: 'For horizontal lists',
|
|
direction: 'horizontal',
|
|
},
|
|
grid: {
|
|
name: 'rectSortingStrategy',
|
|
description: 'For grid layouts',
|
|
direction: 'both',
|
|
},
|
|
};
|
|
|
|
return strategies[type] || strategies.vertical;
|
|
}
|
|
|
|
// Generate accessibility configuration
|
|
function generateAccessibilityConfig(options = {}) {
|
|
const {
|
|
announcements = true,
|
|
liveRegion = true,
|
|
screenReaderInstructions = true,
|
|
ariaDescribedBy = 'dnd-instructions',
|
|
} = options;
|
|
|
|
const config = {
|
|
announcements: announcements ? {
|
|
onDragStart: (id) => `Picked up draggable item ${id}`,
|
|
onDragOver: (id, overId) => overId
|
|
? `Draggable item ${id} is over droppable area ${overId}`
|
|
: `Draggable item ${id} is no longer over a droppable area`,
|
|
onDragEnd: (id, overId) => overId
|
|
? `Draggable item ${id} was dropped over droppable area ${overId}`
|
|
: `Draggable item ${id} was dropped`,
|
|
onDragCancel: (id) => `Dragging was cancelled. Draggable item ${id} was dropped`,
|
|
} : null,
|
|
liveRegion,
|
|
screenReaderInstructions,
|
|
ariaDescribedBy,
|
|
ariaAttributes: {
|
|
role: 'application',
|
|
'aria-roledescription': 'Sortable list',
|
|
},
|
|
};
|
|
|
|
return config;
|
|
}
|
|
|
|
// Generate auto-scroll configuration
|
|
function generateAutoScrollConfig(options = {}) {
|
|
const {
|
|
enabled = true,
|
|
threshold = 0.2, // 20% from edge
|
|
maxSpeed = 10,
|
|
acceleration = 10,
|
|
interval = 10,
|
|
canScroll = true,
|
|
} = options;
|
|
|
|
if (!enabled) return null;
|
|
|
|
return {
|
|
enabled,
|
|
threshold: typeof threshold === 'number' ? { x: threshold, y: threshold } : threshold,
|
|
maxSpeed,
|
|
acceleration,
|
|
interval,
|
|
canScroll: typeof canScroll === 'boolean' ? () => canScroll : canScroll,
|
|
};
|
|
}
|
|
|
|
// Generate complete dnd-kit configuration
|
|
function generateCompleteConfig(presetOrOptions = 'default') {
|
|
const presets = {
|
|
default: {
|
|
sensors: { enablePointer: true, enableKeyboard: true },
|
|
modifiers: { restrictToWindowEdges: true },
|
|
animation: { duration: 200 },
|
|
collision: 'closestCenter',
|
|
sorting: 'vertical',
|
|
accessibility: { announcements: true },
|
|
autoScroll: { enabled: true },
|
|
},
|
|
kanban: {
|
|
sensors: { enablePointer: true, enableKeyboard: true, enableTouch: true },
|
|
modifiers: { restrictToWindowEdges: false },
|
|
animation: { duration: 250, dragOverlay: true },
|
|
collision: 'rectIntersection',
|
|
sorting: 'vertical',
|
|
accessibility: { announcements: true },
|
|
autoScroll: { enabled: true, threshold: 0.15 },
|
|
},
|
|
grid: {
|
|
sensors: { enablePointer: true, enableKeyboard: false },
|
|
modifiers: { snapToGrid: true, gridSize: 100 },
|
|
animation: { duration: 150 },
|
|
collision: 'closestCenter',
|
|
sorting: 'grid',
|
|
accessibility: { announcements: true },
|
|
autoScroll: { enabled: false },
|
|
},
|
|
mobile: {
|
|
sensors: {
|
|
enablePointer: false,
|
|
enableTouch: true,
|
|
touchActivationConstraint: { delay: 300, tolerance: 10 },
|
|
},
|
|
modifiers: { restrictToVerticalAxis: true },
|
|
animation: { duration: 300 },
|
|
collision: 'closestCenter',
|
|
sorting: 'vertical',
|
|
accessibility: { announcements: false },
|
|
autoScroll: { enabled: true, maxSpeed: 5 },
|
|
},
|
|
};
|
|
|
|
const config = typeof presetOrOptions === 'string'
|
|
? presets[presetOrOptions] || presets.default
|
|
: presetOrOptions;
|
|
|
|
return {
|
|
sensors: generateSensorConfig(config.sensors),
|
|
modifiers: generateModifierConfig(config.modifiers),
|
|
animation: generateAnimationConfig(config.animation),
|
|
collision: generateCollisionConfig(config.collision),
|
|
sorting: generateSortingStrategy(config.sorting),
|
|
accessibility: generateAccessibilityConfig(config.accessibility),
|
|
autoScroll: generateAutoScrollConfig(config.autoScroll),
|
|
};
|
|
}
|
|
|
|
// Generate TypeScript types for configuration
|
|
function generateTypeScriptTypes() {
|
|
return `
|
|
// dnd-kit Configuration Types
|
|
export interface DndConfig {
|
|
sensors: SensorConfig[];
|
|
modifiers: ModifierConfig[];
|
|
animation: AnimationConfig;
|
|
collision: CollisionConfig;
|
|
sorting: SortingConfig;
|
|
accessibility: AccessibilityConfig;
|
|
autoScroll: AutoScrollConfig | null;
|
|
}
|
|
|
|
export interface SensorConfig {
|
|
sensor: 'PointerSensor' | 'KeyboardSensor' | 'TouchSensor';
|
|
options?: {
|
|
activationConstraint?: {
|
|
distance?: number;
|
|
delay?: number;
|
|
tolerance?: number;
|
|
};
|
|
coordinateGetter?: string;
|
|
};
|
|
}
|
|
|
|
export interface ModifierConfig {
|
|
modifier: string;
|
|
options?: Record<string, any>;
|
|
}
|
|
|
|
export interface AnimationConfig {
|
|
duration: number;
|
|
easing: string;
|
|
dragOverlay: {
|
|
opacity: number;
|
|
scale: number;
|
|
} | null;
|
|
dropAnimation: {
|
|
duration: number;
|
|
easing: string;
|
|
keyframes: Keyframe[];
|
|
} | null;
|
|
layoutAnimation: {
|
|
duration: number;
|
|
easing: string;
|
|
} | null;
|
|
}
|
|
|
|
export interface CollisionConfig {
|
|
name: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface SortingConfig {
|
|
name: string;
|
|
description: string;
|
|
direction: 'vertical' | 'horizontal' | 'both';
|
|
}
|
|
|
|
export interface AccessibilityConfig {
|
|
announcements: {
|
|
onDragStart: (id: string) => string;
|
|
onDragOver: (id: string, overId?: string) => string;
|
|
onDragEnd: (id: string, overId?: string) => string;
|
|
onDragCancel: (id: string) => string;
|
|
} | null;
|
|
liveRegion: boolean;
|
|
screenReaderInstructions: boolean;
|
|
ariaDescribedBy: string;
|
|
ariaAttributes: Record<string, string>;
|
|
}
|
|
|
|
export interface AutoScrollConfig {
|
|
enabled: boolean;
|
|
threshold: { x: number; y: number };
|
|
maxSpeed: number;
|
|
acceleration: number;
|
|
interval: number;
|
|
canScroll: () => boolean;
|
|
}
|
|
`;
|
|
}
|
|
|
|
// Main function for CLI usage
|
|
if (require.main === module) {
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.length === 0 || args[0] === '--help') {
|
|
console.log(`
|
|
Generate dnd-kit Configuration
|
|
|
|
Usage:
|
|
node generate_dnd_config.js [preset] [--format json|js|ts]
|
|
|
|
Presets:
|
|
default Basic sortable list configuration
|
|
kanban Kanban board configuration
|
|
grid Grid layout configuration
|
|
mobile Mobile-optimized configuration
|
|
|
|
Options:
|
|
--format Output format (json, js, or ts)
|
|
--help Show this help message
|
|
|
|
Examples:
|
|
node generate_dnd_config.js default
|
|
node generate_dnd_config.js kanban --format json
|
|
node generate_dnd_config.js grid --format ts
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
|
|
const preset = args[0];
|
|
const formatIndex = args.indexOf('--format');
|
|
const format = formatIndex !== -1 ? args[formatIndex + 1] : 'json';
|
|
|
|
const config = generateCompleteConfig(preset);
|
|
|
|
switch (format) {
|
|
case 'json':
|
|
console.log(JSON.stringify(config, null, 2));
|
|
break;
|
|
|
|
case 'js':
|
|
console.log(`// dnd-kit configuration for ${preset}
|
|
export const dndConfig = ${JSON.stringify(config, null, 2)};`);
|
|
break;
|
|
|
|
case 'ts':
|
|
console.log(generateTypeScriptTypes());
|
|
console.log(`
|
|
// dnd-kit configuration for ${preset}
|
|
export const dndConfig: DndConfig = ${JSON.stringify(config, null, 2)};`);
|
|
break;
|
|
|
|
default:
|
|
console.error(`Unknown format: ${format}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Export functions for use in other scripts
|
|
module.exports = {
|
|
generateSensorConfig,
|
|
generateModifierConfig,
|
|
generateAnimationConfig,
|
|
generateCollisionConfig,
|
|
generateSortingStrategy,
|
|
generateAccessibilityConfig,
|
|
generateAutoScrollConfig,
|
|
generateCompleteConfig,
|
|
generateTypeScriptTypes,
|
|
}; |