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
165 lines
5.1 KiB
JavaScript
165 lines
5.1 KiB
JavaScript
var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
|
|
var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
|
|
|
|
// magical touchAction value
|
|
var TOUCH_ACTION_COMPUTE = 'compute';
|
|
var TOUCH_ACTION_AUTO = 'auto';
|
|
var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
|
|
var TOUCH_ACTION_NONE = 'none';
|
|
var TOUCH_ACTION_PAN_X = 'pan-x';
|
|
var TOUCH_ACTION_PAN_Y = 'pan-y';
|
|
var TOUCH_ACTION_MAP = getTouchActionProps();
|
|
|
|
/**
|
|
* Touch Action
|
|
* sets the touchAction property or uses the js alternative
|
|
* @param {Manager} manager
|
|
* @param {String} value
|
|
* @constructor
|
|
*/
|
|
function TouchAction(manager, value) {
|
|
this.manager = manager;
|
|
this.set(value);
|
|
}
|
|
|
|
TouchAction.prototype = {
|
|
/**
|
|
* set the touchAction value on the element or enable the polyfill
|
|
* @param {String} value
|
|
*/
|
|
set: function(value) {
|
|
// find out the touch-action by the event handlers
|
|
if (value == TOUCH_ACTION_COMPUTE) {
|
|
value = this.compute();
|
|
}
|
|
|
|
if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
|
|
this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
|
|
}
|
|
this.actions = value.toLowerCase().trim();
|
|
},
|
|
|
|
/**
|
|
* just re-set the touchAction value
|
|
*/
|
|
update: function() {
|
|
this.set(this.manager.options.touchAction);
|
|
},
|
|
|
|
/**
|
|
* compute the value for the touchAction property based on the recognizer's settings
|
|
* @returns {String} value
|
|
*/
|
|
compute: function() {
|
|
var actions = [];
|
|
each(this.manager.recognizers, function(recognizer) {
|
|
if (boolOrFn(recognizer.options.enable, [recognizer])) {
|
|
actions = actions.concat(recognizer.getTouchAction());
|
|
}
|
|
});
|
|
return cleanTouchActions(actions.join(' '));
|
|
},
|
|
|
|
/**
|
|
* this method is called on each input cycle and provides the preventing of the browser behavior
|
|
* @param {Object} input
|
|
*/
|
|
preventDefaults: function(input) {
|
|
var srcEvent = input.srcEvent;
|
|
var direction = input.offsetDirection;
|
|
|
|
// if the touch action did prevented once this session
|
|
if (this.manager.session.prevented) {
|
|
srcEvent.preventDefault();
|
|
return;
|
|
}
|
|
|
|
var actions = this.actions;
|
|
var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
|
|
var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
|
|
var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
|
|
|
|
if (hasNone) {
|
|
//do not prevent defaults if this is a tap gesture
|
|
|
|
var isTapPointer = input.pointers.length === 1;
|
|
var isTapMovement = input.distance < 2;
|
|
var isTapTouchTime = input.deltaTime < 250;
|
|
|
|
if (isTapPointer && isTapMovement && isTapTouchTime) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (hasPanX && hasPanY) {
|
|
// `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
|
|
return;
|
|
}
|
|
|
|
if (hasNone ||
|
|
(hasPanY && direction & DIRECTION_HORIZONTAL) ||
|
|
(hasPanX && direction & DIRECTION_VERTICAL)) {
|
|
return this.preventSrc(srcEvent);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* call preventDefault to prevent the browser's default behavior (scrolling in most cases)
|
|
* @param {Object} srcEvent
|
|
*/
|
|
preventSrc: function(srcEvent) {
|
|
this.manager.session.prevented = true;
|
|
srcEvent.preventDefault();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* when the touchActions are collected they are not a valid value, so we need to clean things up. *
|
|
* @param {String} actions
|
|
* @returns {*}
|
|
*/
|
|
function cleanTouchActions(actions) {
|
|
// none
|
|
if (inStr(actions, TOUCH_ACTION_NONE)) {
|
|
return TOUCH_ACTION_NONE;
|
|
}
|
|
|
|
var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
|
|
var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
|
|
|
|
// if both pan-x and pan-y are set (different recognizers
|
|
// for different directions, e.g. horizontal pan but vertical swipe?)
|
|
// we need none (as otherwise with pan-x pan-y combined none of these
|
|
// recognizers will work, since the browser would handle all panning
|
|
if (hasPanX && hasPanY) {
|
|
return TOUCH_ACTION_NONE;
|
|
}
|
|
|
|
// pan-x OR pan-y
|
|
if (hasPanX || hasPanY) {
|
|
return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
|
|
}
|
|
|
|
// manipulation
|
|
if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
|
|
return TOUCH_ACTION_MANIPULATION;
|
|
}
|
|
|
|
return TOUCH_ACTION_AUTO;
|
|
}
|
|
|
|
function getTouchActionProps() {
|
|
if (!NATIVE_TOUCH_ACTION) {
|
|
return false;
|
|
}
|
|
var touchMap = {};
|
|
var cssSupports = window.CSS && window.CSS.supports;
|
|
['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
|
|
|
|
// If css.supports is not supported but there is native touch-action assume it supports
|
|
// all values. This is the case for IE 10 and 11.
|
|
touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
|
|
});
|
|
return touchMap;
|
|
}
|