feat: Phase 2.5 - Variant Collections Spreadsheet Editor
Replaces JSON textarea with professional Excel-like spreadsheet interface for managing product variant properties. Features: - Handsontable 14.6.1 spreadsheet component - Property presets (Size, Color, Material, Storage, Custom) - Inline cell editing with Tab/Enter navigation - Context menu for add/remove rows and columns - Keyboard shortcuts (Ctrl+D delete, Ctrl+Enter save, Ctrl+Z undo) - Mobile touch gestures (swipe to delete rows) - Automatic JSON serialization on form submit - Form validation before saving - Comprehensive user guide documentation Files Changed: - LittleShop/package.json: NPM package management setup - LittleShop/wwwroot/js/variant-editor.js: 400-line spreadsheet editor module - LittleShop/wwwroot/lib/handsontable/: Handsontable library (Community Edition) - LittleShop/wwwroot/lib/hammerjs/: Hammer.js touch gesture library - LittleShop/Areas/Admin/Views/VariantCollections/Edit.cshtml: Spreadsheet UI integration - VARIANT_COLLECTIONS_USER_GUIDE.md: Complete user guide (18+ pages) Technical Details: - Excel-like editing experience (no more manual JSON editing) - Mobile-first responsive design - Browser compatibility: Chrome 90+, Firefox 88+, Edge 90+, Safari 14+ - Touch-optimized for mobile administration - Automatic data validation and error handling
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
var el,
|
||||
hammer;
|
||||
|
||||
module('Pan Gesture', {
|
||||
setup: function() {
|
||||
el = document.createElement('div');
|
||||
document.body.appendChild(el);
|
||||
|
||||
hammer = new Hammer(el, {recognizers: []});
|
||||
},
|
||||
teardown: function() {
|
||||
document.body.removeChild(el);
|
||||
hammer.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
test('`panstart` and `panmove` should be recognized', function() {
|
||||
expect(2);
|
||||
|
||||
var panMoveCount = 0;
|
||||
var pan = new Hammer.Pan({threshold: 1});
|
||||
|
||||
hammer.add(pan);
|
||||
hammer.on('panstart', function() {
|
||||
ok(true);
|
||||
});
|
||||
hammer.on('panmove', function() {
|
||||
panMoveCount++;
|
||||
});
|
||||
|
||||
utils.dispatchTouchEvent(el, 'start', 50, 50);
|
||||
utils.dispatchTouchEvent(el, 'move', 70, 50);
|
||||
utils.dispatchTouchEvent(el, 'move', 90, 50);
|
||||
|
||||
equal(panMoveCount, 1);
|
||||
});
|
||||
|
||||
asyncTest('Pan event flow should be start -> left -> end', function() {
|
||||
expect(1);
|
||||
var pan = new Hammer.Pan({threshold: 1});
|
||||
hammer.add(pan);
|
||||
|
||||
var eventflow = "";
|
||||
var isCalledPanleft = false;
|
||||
hammer.on('panstart', function() {
|
||||
eventflow += "start";
|
||||
});
|
||||
hammer.on('panleft', function() {
|
||||
if(!isCalledPanleft){
|
||||
isCalledPanleft = true;
|
||||
eventflow += "left";
|
||||
}
|
||||
});
|
||||
hammer.on('panend', function() {
|
||||
eventflow += "end";
|
||||
isCalledPanleft = true;
|
||||
});
|
||||
|
||||
Simulator.gestures.pan(el, { deltaX: -100, deltaY: 0 }, function() {
|
||||
equal(eventflow,"startleftend");
|
||||
start();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
var el,
|
||||
hammer;
|
||||
|
||||
module('Pinch Gesture', {
|
||||
setup: function() {
|
||||
el = document.createElement('div');
|
||||
document.body.appendChild(el);
|
||||
|
||||
hammer = new Hammer(el, {recognizers: []});
|
||||
},
|
||||
teardown: function() {
|
||||
document.body.removeChild(el);
|
||||
hammer.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
asyncTest('Pinch event flow should be start -> in -> end', function() {
|
||||
expect(1);
|
||||
var pinch = new Hammer.Pinch({enable: true, threshold: .1});
|
||||
hammer.add(pinch);
|
||||
|
||||
var eventflow = "";
|
||||
var isFiredPinchin = false;
|
||||
hammer.on('pinchstart', function() {
|
||||
eventflow += "start";
|
||||
});
|
||||
hammer.on('pinchin', function() {
|
||||
if(!isFiredPinchin){
|
||||
isFiredPinchin = true;
|
||||
eventflow += "in";
|
||||
}
|
||||
});
|
||||
hammer.on('pinchend', function() {
|
||||
eventflow += "end";
|
||||
isFiredPinchin = false;
|
||||
});
|
||||
|
||||
Simulator.gestures.pinch(el, { duration: 500, scale: .5 }, function() {
|
||||
equal(eventflow,"startinend");
|
||||
start();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
var el,
|
||||
hammer,
|
||||
swipeCount = 0;
|
||||
|
||||
module('Swipe Gesture', {
|
||||
setup: function() {
|
||||
el = utils.createHitArea();
|
||||
hammer = new Hammer(el, {recognizers: []});
|
||||
swipeCount = 0;
|
||||
},
|
||||
teardown: function() {
|
||||
hammer.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
test('swipe can be recognized', function() {
|
||||
expect(1);
|
||||
|
||||
var swipe = new Hammer.Swipe({threshold: 1});
|
||||
hammer.add(swipe);
|
||||
hammer.on('swipe', function() {
|
||||
ok(true);
|
||||
start();
|
||||
});
|
||||
|
||||
stop();
|
||||
|
||||
Simulator.gestures.swipe(el);
|
||||
});
|
||||
Reference in New Issue
Block a user