@themarioga/grid-editor 3.2.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/BUILDING.md +12 -3
  2. package/CHANGELOG.md +45 -0
  3. package/README.md +26 -6
  4. package/UPGRADING.md +79 -0
  5. package/dist/grideditor.css +40 -20
  6. package/dist/grideditor.min.css +1 -1
  7. package/dist/grideditor.min.css.map +1 -1
  8. package/dist/jquery.grideditor.bundle.min.js +10 -0
  9. package/dist/jquery.grideditor.js +369 -165
  10. package/dist/jquery.grideditor.min.js +1 -1
  11. package/dist/jquery.grideditor.min.js.map +1 -1
  12. package/dist/locales/grideditor.es.js +2 -0
  13. package/dist/locales/grideditor.es.min.js +1 -1
  14. package/dist/locales/grideditor.es.min.js.map +1 -1
  15. package/dist/plugins/grideditor.accordion.js +8 -0
  16. package/dist/plugins/grideditor.accordion.min.js +1 -1
  17. package/dist/plugins/grideditor.accordion.min.js.map +1 -1
  18. package/dist/plugins/grideditor.elements.js +5 -5
  19. package/dist/plugins/grideditor.elements.min.js +1 -1
  20. package/dist/plugins/grideditor.elements.min.js.map +1 -1
  21. package/dist/plugins/grideditor.tabs.js +8 -0
  22. package/dist/plugins/grideditor.tabs.min.js +1 -1
  23. package/dist/plugins/grideditor.tabs.min.js.map +1 -1
  24. package/docs/events.md +6 -7
  25. package/docs/locale-keys.md +2 -0
  26. package/docs/plugins.md +21 -7
  27. package/example/autosave.html +1 -1
  28. package/example/basic.html +3 -2
  29. package/example/breakpoints.html +1 -1
  30. package/example/ckeditor.html +1 -1
  31. package/example/containers.html +1 -1
  32. package/example/elements.html +1 -1
  33. package/example/locale.html +1 -1
  34. package/example/plugins.html +1 -1
  35. package/example/summernote.html +1 -1
  36. package/example/wrap_content.html +1 -1
  37. package/package.json +3 -2
  38. package/src/js/jquery.grideditor.js +369 -165
  39. package/src/js/locales/grideditor.es.js +2 -0
  40. package/src/js/plugins/grideditor.accordion.js +8 -0
  41. package/src/js/plugins/grideditor.elements.js +5 -5
  42. package/src/js/plugins/grideditor.tabs.js +8 -0
  43. package/src/less/grideditor.less +43 -18
@@ -111,13 +111,29 @@ var NESTED_SETTINGS = {
111
111
  },
112
112
  resize: {
113
113
  enabled: true,
114
- handles: 'e', // Which edges carry a handle, as jQuery UI names them
114
+ handles: 'e', // Which edges carry a handle: 'e', 'w', or 'e, w'
115
115
  balance: 'next', // 'next' takes the units out of the following column
116
116
  },
117
+ drag: {
118
+ delay: 0, // Milliseconds to hold before a drag starts
119
+ touch_delay: 100, // The same for touch, where 0 eats the page's scrolling
120
+ threshold: 3, // Pixels of movement before a gesture counts as a drag
121
+ animation: 150, // Milliseconds of reordering animation, 0 for none
122
+ scroll: true, // Scroll the page when a drag reaches its edge
123
+ },
124
+ };
125
+
126
+ /** Settings 4.0 took away, and what a host should reach for instead. */
127
+ var REMOVED_SETTINGS = {
128
+ sortable_options: 'drag',
129
+ resizable_options: 'resize',
117
130
  };
118
131
 
119
132
  var warned = {};
120
133
 
134
+ /** Editors on the page, counted so each one's sortable groups are its own. */
135
+ var editorCounter = 0;
136
+
121
137
  /**
122
138
  * Translate one key.
123
139
  *
@@ -252,13 +268,12 @@ $.fn.gridEditor = function( optionsOrMethod ) {
252
268
  'layout_modes' : VIEW_KEYS.slice(), // Which views the dropdown offers
253
269
  'default_view' : ALL_VIEW,
254
270
  'resize' : NESTED_SETTINGS.resize, // Resizing a column by dragging its edge
255
- 'resizable_options' : {}, // Merged into every jQuery UI resizable
256
271
  'source_textarea' : '',
257
272
  'locale' : 'en', // Code of a locale in $.fn.gridEditor.locales
258
273
  'locale_strings' : {}, // Overrides for individual keys
259
274
  'callbacks' : {}, // before_*/after_* functions, the events by another route
260
275
  'confirm_delete' : true, // Ask before deleting a row or a column
261
- 'sortable_options' : {} // Merged into every jQuery UI sortable
276
+ 'drag' : NESTED_SETTINGS.drag // How a drag behaves, whatever drives it
262
277
  }, optionsOrMethod);
263
278
 
264
279
  // Merged rather than replaced, so `elements: { auto: true }` keeps the
@@ -267,6 +282,18 @@ $.fn.gridEditor = function( optionsOrMethod ) {
267
282
  settings[name] = $.extend({}, defaults, settings[name]);
268
283
  });
269
284
 
285
+ // Both handed out the drag toolkit's own options, which 4.0 stops
286
+ // promising: there is no widget underneath a host should be reaching
287
+ // for. What they were used for is a setting of the editor's now.
288
+ $.each(REMOVED_SETTINGS, function(name, replacement) {
289
+ if (optionsOrMethod && optionsOrMethod[name] !== undefined) {
290
+ warn($.fn.gridEditor.t(settings, 'warning.setting_removed', {
291
+ setting: name,
292
+ replacement: replacement,
293
+ }));
294
+ }
295
+ });
296
+
270
297
 
271
298
  // Elems
272
299
  var canvas,
@@ -282,6 +309,8 @@ $.fn.gridEditor = function( optionsOrMethod ) {
282
309
  var sizePicker = null; // The open column size picker, if there is one
283
310
  var dropMarker = null; // The line showing where a dragged toolbar button would land
284
311
  var warnedHere = {}; // Deprecations are worth saying once per instance, not once per call
312
+ var sortables = []; // Every list made sortable, so deinit destroys exactly those
313
+ var instanceId = ++editorCounter; // Scopes the sortable groups to this editor
285
314
 
286
315
  // Before anything else, because the instance handle hands the canvas
287
316
  // to hosts and the rest of setup() runs at the end of this function
@@ -874,26 +903,82 @@ $.fn.gridEditor = function( optionsOrMethod ) {
874
903
  }
875
904
 
876
905
  function makeToolbarDraggable() {
906
+ var buttons = mainControls.find('[data-ge-toolbar]')
907
+ .removeClass('ge-palette-button')
908
+ .off('pointerdown.ge-palette')
909
+ ;
910
+
877
911
  if (!toolbarDrags()) { return; }
878
912
 
879
- mainControls.find('[data-ge-toolbar]').draggable({
880
- helper: function() { return $(this).clone().addClass('ge-toolbar-helper'); },
881
- appendTo: 'body',
882
- zIndex: 1000,
883
- cursorAt: { top: 14, left: 14 },
913
+ buttons.addClass('ge-palette-button').on('pointerdown.ge-palette', startToolbarDrag);
914
+ }
884
915
 
885
- start: function() { canvas.addClass('ge-dropping'); },
886
- drag: function(e) { showDropMarker(e.pageX, e.pageY); },
916
+ /**
917
+ * A toolbar button carried onto the canvas.
918
+ *
919
+ * Nothing happens until the pointer has moved far enough to mean it:
920
+ * below that the gesture is a click, and the button's own handler
921
+ * adds the block at the end of the canvas as it always has - which is
922
+ * also what keeps the add column tool's hold-to-pick working.
923
+ */
924
+ function startToolbarDrag(e) {
925
+ var button = $(e.currentTarget);
926
+ var startX = e.pageX;
927
+ var startY = e.pageY;
928
+ var helper = null;
887
929
 
888
- stop: function(e) {
889
- var where = dropPlaceAt(e.pageX, e.pageY);
930
+ if (e.button) { return; }
890
931
 
891
- canvas.removeClass('ge-dropping');
892
- hideDropMarker();
932
+ function far(move) {
933
+ return Math.abs(move.pageX - startX) + Math.abs(move.pageY - startY) >
934
+ settings.drag.threshold;
935
+ }
893
936
 
894
- if (where) { insertFromToolbar($(this), where); }
895
- },
896
- });
937
+ function onMove(move) {
938
+ if (!helper) {
939
+ if (!far(move)) { return; }
940
+
941
+ helper = button.clone()
942
+ .addClass('ge-toolbar-helper')
943
+ .appendTo('body')
944
+ ;
945
+ canvas.addClass('ge-dropping');
946
+ }
947
+
948
+ helper.css({ left: move.pageX - 14, top: move.pageY - 14 });
949
+ showDropMarker(move.pageX, move.pageY);
950
+ }
951
+
952
+ function onUp(up) {
953
+ $(document)
954
+ .off('pointermove.ge-toolbar', onMove)
955
+ .off('pointerup.ge-toolbar pointercancel.ge-toolbar', onUp)
956
+ ;
957
+
958
+ if (!helper) { return; }
959
+
960
+ helper.remove();
961
+ canvas.removeClass('ge-dropping');
962
+ hideDropMarker();
963
+
964
+ // The click that follows a drag would add the block a second
965
+ // time, at the end of the canvas
966
+ button.one('click', function(click) {
967
+ click.preventDefault();
968
+ click.stopImmediatePropagation();
969
+ });
970
+
971
+ var where = dropPlaceAt(up.pageX, up.pageY);
972
+ if (where) { insertFromToolbar(button, where); }
973
+ }
974
+
975
+ // On the document, not on the button: until the gesture is far
976
+ // enough along to be a drag there is nothing to capture the
977
+ // pointer with, and the pointer has left the button by then
978
+ $(document)
979
+ .on('pointermove.ge-toolbar', onMove)
980
+ .on('pointerup.ge-toolbar pointercancel.ge-toolbar', onUp)
981
+ ;
897
982
  }
898
983
 
899
984
  /**
@@ -1526,9 +1611,9 @@ $.fn.gridEditor = function( optionsOrMethod ) {
1526
1611
 
1527
1612
  var room = spare(row, leadingTier());
1528
1613
 
1529
- // The drawer it hangs off is raised while it is open: every
1530
- // drawer sits above jQuery UI's handles, so without this the
1531
- // drawer of the column below takes the clicks meant for the picker
1614
+ // The drawer it hangs off is raised while it is open: drawers sit
1615
+ // below the resize handles, so without this the drawer of the
1616
+ // column below takes the clicks meant for the picker
1532
1617
  tool.closest('.ge-tools-drawer').addClass('ge-picker-open');
1533
1618
 
1534
1619
  sizePicker = $('<div class="ge-size-picker" />').appendTo(tool);
@@ -1981,9 +2066,10 @@ $.fn.gridEditor = function( optionsOrMethod ) {
1981
2066
  }
1982
2067
 
1983
2068
  /**
1984
- * Drag resize leaves an inline pixel width behind, and jQuery UI adds
1985
- * its own. Neither belongs in the markup a host saves, or in the
1986
- * canvas once the size class has been written.
2069
+ * Drag resize leaves an inline pixel width behind: the column follows
2070
+ * the pointer in pixels while the gesture lasts. It does not belong in
2071
+ * the markup a host saves, or in the canvas once the size class has
2072
+ * been written.
1987
2073
  */
1988
2074
  function stripPixelWidths(scope) {
1989
2075
  scope.find('.column').addBack('.column').each(function() {
@@ -1997,112 +2083,196 @@ $.fn.gridEditor = function( optionsOrMethod ) {
1997
2083
  });
1998
2084
  }
1999
2085
 
2000
- function makeSortable() {
2001
- var wholeDrawer = settings.drag_handle === 'drawer';
2002
-
2003
- var shared = {
2004
- handle: wholeDrawer ? '> .ge-tools-drawer' : '> .ge-tools-drawer .ge-move',
2086
+ /**
2087
+ * A group name, scoped to this editor.
2088
+ *
2089
+ * Two editors on one page must not drag into each other, since a node
2090
+ * carries its drawer - and the events that go with it - from the
2091
+ * editor that made it.
2092
+ */
2093
+ function groupName(name) {
2094
+ return 'ge-' + name + '-' + instanceId;
2095
+ }
2005
2096
 
2097
+ /** What must never start a drag, whatever the handle is. */
2098
+ function dragCancelSelector() {
2099
+ return settings.drag_handle === 'drawer'
2006
2100
  // With the whole drawer as the handle, the tools inside it are
2007
2101
  // still tools: a drag starting on one would swallow its click,
2008
2102
  // and the settings panel has fields to type in
2009
- cancel: wholeDrawer
2010
- ? '.ge-tools-drawer > a, .ge-details, input, textarea, button, select, option'
2011
- : 'input, textarea, button, select, option',
2103
+ ? '.ge-tools-drawer > a, .ge-details, input, textarea, button, select, option'
2104
+ : 'input, textarea, button, select, option';
2105
+ }
2012
2106
 
2013
- start: sortStart,
2014
- stop: sortStop,
2015
- helper: 'clone',
2016
- };
2107
+ /**
2108
+ * Every sortable list the editor makes, core's and a plugin's alike,
2109
+ * is made here.
2110
+ *
2111
+ * A caller says what moves (`draggable`) and what the list connects to
2112
+ * (`group`, or none for a list that sorts only within itself); the
2113
+ * handle, the filter, the callbacks and the drag settings are this
2114
+ * function's business. That is the whole point of it: the toolkit
2115
+ * underneath is named in one place, so replacing it is one function
2116
+ * and not thirty call sites.
2117
+ */
2118
+ function sortable(lists, options) {
2119
+ if (!lists.length || !window.Sortable) { return; }
2017
2120
 
2018
- canvas.find('.row').sortable($.extend({
2019
- items: '> .column',
2020
- connectWith: '.ge-canvas .row',
2021
- tolerance: 'pointer',
2022
- }, shared, settings.sortable_options));
2121
+ var wholeDrawer = settings.drag_handle === 'drawer';
2122
+ var cancel = dragCancelSelector();
2123
+ var soloGroup = 0;
2124
+
2125
+ lists.each(function() {
2126
+ var list = this;
2127
+
2128
+ // A list with no group sorts only within itself, which is what
2129
+ // a tab strip wants: a name of its own, closed both ways
2130
+ var group = options.group
2131
+ ? { name: groupName(options.group) }
2132
+ : { name: groupName(options.draggable + '-' + (++soloGroup)), pull: false, put: false };
2133
+
2134
+ sortables.push(window.Sortable.create(list, $.extend({
2135
+ group: group,
2136
+ draggable: options.draggable,
2137
+ handle: wholeDrawer ? '.ge-tools-drawer' : '.ge-tools-drawer .ge-move',
2138
+
2139
+ /**
2140
+ * Two refusals in one, because SortableJS asks once.
2141
+ *
2142
+ * A tool or a form field never starts a drag, as the
2143
+ * cancel list said before. And only a direct child of
2144
+ * this list moves: the selector is matched against every
2145
+ * descendant, so without this the canvas would pick up a
2146
+ * row nested three columns down and drag that.
2147
+ */
2148
+ filter: function(e, item) {
2149
+ if ($(e.target).closest(cancel, list).length) { return true; }
2150
+
2151
+ return !item || item.parentNode !== list;
2152
+ },
2153
+
2154
+ // A filtered pointerdown is still a click on a tool
2155
+ preventOnFilter: false,
2156
+
2157
+ // The HTML5 drag and drop API cannot be driven by
2158
+ // synthetic events, so the tests could not exist without
2159
+ // this; it also gives one helper across browsers
2160
+ forceFallback: true,
2161
+ fallbackOnBody: true,
2162
+
2163
+ ghostClass: 'ge-drag-placeholder',
2164
+ chosenClass: 'ge-drag-chosen',
2165
+ dragClass: 'ge-drag-helper',
2166
+ fallbackClass: 'ge-drag-helper',
2167
+
2168
+ animation: settings.drag.animation,
2169
+
2170
+ // One delay, which applies to both gestures or to touch
2171
+ // alone: a touch drag that starts instantly takes the
2172
+ // page's scrolling with it, a mouse drag has no such
2173
+ // problem, and asking for `delay` means asking for both
2174
+ delay: settings.drag.delay || settings.drag.touch_delay,
2175
+ delayOnTouchOnly: !settings.drag.delay,
2176
+
2177
+ touchStartThreshold: settings.drag.threshold,
2178
+ scroll: settings.drag.scroll,
2179
+
2180
+ onStart: sortStart,
2181
+ onEnd: sortEnd,
2182
+ }, options.options || {})));
2183
+ });
2184
+ }
2023
2185
 
2024
- canvas.add(canvas.find('.column')).sortable($.extend({
2025
- items: '> .row, > .ge-content',
2026
- connectWith: '.ge-canvas, .ge-canvas .column',
2027
- }, shared, settings.sortable_options));
2186
+ function makeSortable() {
2187
+ if (!window.Sortable) {
2188
+ warnOnceHere('sortable_missing', t('error.sortable_missing'));
2189
+ return;
2190
+ }
2028
2191
 
2029
- // A tab strip sorts its own tabs, and the panes follow them
2030
- canvas.find('.ge-container-tabs > .nav-tabs').sortable($.extend({
2031
- items: '> .ge-tab',
2032
- }, shared, settings.sortable_options));
2192
+ sortable(canvas.find('.row'), {
2193
+ draggable: '.column',
2194
+ group: 'column',
2195
+ });
2033
2196
 
2034
- // Accordion items sort within their accordion and into any other
2035
- canvas.find('.ge-container-accordion > .accordion').sortable($.extend({
2036
- items: '> .ge-accordion-item',
2037
- connectWith: '.ge-canvas .ge-container-accordion > .accordion',
2038
- }, shared, settings.sortable_options));
2197
+ sortable(canvas.add(canvas.find('.column')), {
2198
+ draggable: '.row, .ge-content, [data-ge-container]',
2199
+ group: 'block',
2200
+ });
2039
2201
 
2040
2202
  // A plugin makes its own: only it knows which of its parts move
2041
- plugins('onSortable', shared);
2042
-
2043
- /**
2044
- * jQuery UI cannot refuse a drag once it has started, so a
2045
- * canceled before-move is remembered here and undone on drop
2046
- * (spec 2.4). The node carries the mark, because with connected
2047
- * lists the drop is not always reported by the list that started
2048
- * the drag.
2049
- */
2050
- function sortStart(e, ui) {
2051
- ui.placeholder.css({ height: ui.item.outerHeight()});
2052
-
2053
- var node = ui.item;
2054
- var from = positionOf(node);
2055
-
2056
- node.data('ge-move-from', from);
2057
- node.removeData('ge-move-canceled');
2203
+ plugins('onSortable', sortable);
2204
+ }
2058
2205
 
2059
- operate(function() {
2060
- var moving = emit('before-move', payloadFor(kindOf(node), node, {
2061
- parent: from.parent,
2062
- source: 'dragdrop',
2063
- from: from,
2064
- }));
2206
+ /**
2207
+ * A drag cannot be refused once it has started, so a canceled
2208
+ * before-move is remembered here and undone on drop (spec 2.4). The
2209
+ * node carries the mark, because with connected lists the drop is not
2210
+ * always reported by the list that started the drag.
2211
+ */
2212
+ function sortStart(e) {
2213
+ var node = $(e.item);
2214
+ var from = positionOf(node);
2065
2215
 
2066
- if (!moving) { node.data('ge-move-canceled', true); }
2067
- });
2068
- }
2216
+ node.data('ge-move-from', from);
2217
+ node.removeData('ge-move-canceled');
2069
2218
 
2070
- function sortStop(e, ui) {
2071
- var node = ui.item;
2072
- var from = node.data('ge-move-from') || positionOf(node);
2219
+ operate(function() {
2220
+ var moving = emit('before-move', payloadFor(kindOf(node), node, {
2221
+ parent: from.parent,
2222
+ source: 'dragdrop',
2223
+ from: from,
2224
+ }));
2073
2225
 
2074
- node.removeData('ge-move-from');
2226
+ if (!moving) { node.data('ge-move-canceled', true); }
2227
+ });
2228
+ }
2075
2229
 
2076
- if (node.data('ge-move-canceled')) {
2077
- node.removeData('ge-move-canceled');
2078
- $(this).sortable('cancel');
2079
- return;
2080
- }
2230
+ /** Put a node back where a refused drag found it. */
2231
+ function putBack(node, from) {
2232
+ var siblings = from.parent.children().not('.ge-tools-drawer').not(node);
2081
2233
 
2082
- var to = positionOf(node);
2083
- if (to.parent[0] === from.parent[0] && to.index === from.index) {
2084
- return; // A drag that went nowhere is not a move
2085
- }
2234
+ if (!siblings.length || from.index >= siblings.length) {
2235
+ node.appendTo(from.parent);
2236
+ } else {
2237
+ node.insertBefore(siblings.eq(from.index));
2238
+ }
2239
+ }
2086
2240
 
2087
- var container = node.closest('[data-ge-container]');
2088
- var definition = CONTAINERS[containerTypeOf(container)];
2089
- if (definition && definition.afterPaneMove) {
2090
- definition.afterPaneMove(container, node, from);
2091
- }
2241
+ function sortEnd(e) {
2242
+ var node = $(e.item);
2243
+ var from = node.data('ge-move-from') || positionOf(node);
2092
2244
 
2093
- // No init() here, unlike an add: the node brought its drawer
2094
- // with it, and jQuery UI is still finishing the drag, so this
2095
- // is the wrong moment to rebuild the widgets it is using.
2096
- operate(function() {
2097
- emit('after-move', payloadFor(kindOf(node), node, {
2098
- parent: to.parent,
2099
- source: 'dragdrop',
2100
- from: from,
2101
- to: to,
2102
- container: container.length ? container : undefined,
2103
- }));
2104
- });
2245
+ node.removeData('ge-move-from');
2246
+
2247
+ if (node.data('ge-move-canceled')) {
2248
+ node.removeData('ge-move-canceled');
2249
+ putBack(node, from);
2250
+ return;
2251
+ }
2252
+
2253
+ var to = positionOf(node);
2254
+ if (to.parent[0] === from.parent[0] && to.index === from.index) {
2255
+ return; // A drag that went nowhere is not a move
2256
+ }
2257
+
2258
+ var container = node.closest('[data-ge-container]');
2259
+ var definition = CONTAINERS[containerTypeOf(container)];
2260
+ if (definition && definition.afterPaneMove) {
2261
+ definition.afterPaneMove(container, node, from);
2105
2262
  }
2263
+
2264
+ // No init() here, unlike an add: the node brought its drawer
2265
+ // with it, and the drag is still being finished, so this is the
2266
+ // wrong moment to rebuild the lists it is using.
2267
+ operate(function() {
2268
+ emit('after-move', payloadFor(kindOf(node), node, {
2269
+ parent: to.parent,
2270
+ source: 'dragdrop',
2271
+ from: from,
2272
+ to: to,
2273
+ container: container.length ? container : undefined,
2274
+ }));
2275
+ });
2106
2276
  }
2107
2277
 
2108
2278
  /**
@@ -2119,24 +2289,74 @@ $.fn.gridEditor = function( optionsOrMethod ) {
2119
2289
 
2120
2290
  canvas.find('.column').each(function() {
2121
2291
  var col = $(this);
2122
- if (col.data('ui-resizable')) { return; }
2292
+ if (col.find('> .ge-resize-handle').length) { return; }
2123
2293
 
2124
2294
  $('<span class="ge-resize-size" />').appendTo(col.find('> .ge-tools-drawer'));
2125
2295
 
2126
- col.resizable($.extend({
2127
- handles: settings.resize.handles,
2128
- start: resizeStart,
2129
- resize: resizeMove,
2130
- stop: resizeStop,
2131
- }, settings.resizable_options));
2296
+ $.each(resizeEdges(), function(_, edge) {
2297
+ $('<span class="ge-resize-handle" />')
2298
+ .addClass('ge-resize-' + edge)
2299
+ .attr('data-ge-edge', edge)
2300
+ .on('pointerdown', startResizeDrag)
2301
+ .appendTo(col)
2302
+ ;
2303
+ });
2132
2304
  });
2133
2305
  }
2134
2306
 
2307
+ /** Which edges carry a handle: 'e', 'w', or both. */
2308
+ function resizeEdges() {
2309
+ return String(settings.resize.handles).split(',')
2310
+ .map(function(edge) { return edge.trim(); })
2311
+ .filter(function(edge) { return edge === 'e' || edge === 'w'; });
2312
+ }
2313
+
2314
+ /**
2315
+ * One resize gesture, from the pointer going down on a handle to it
2316
+ * coming up again.
2317
+ *
2318
+ * The pointer is captured, so a fast drag that leaves the column
2319
+ * behind keeps resizing it, and a refused before-resize simply never
2320
+ * starts: nothing is written and the column does not move.
2321
+ */
2322
+ function startResizeDrag(e) {
2323
+ var handle = $(e.currentTarget);
2324
+ var col = handle.parent();
2325
+ var west = handle.attr('data-ge-edge') === 'w';
2326
+ var startX = e.pageX;
2327
+ var startWidth = col.outerWidth();
2328
+
2329
+ e.preventDefault();
2330
+
2331
+ if (!resizeStart(col)) { return; }
2332
+
2333
+ col.addClass('ge-resizing');
2334
+ if (e.pointerId !== undefined && handle[0].setPointerCapture) {
2335
+ handle[0].setPointerCapture(e.pointerId);
2336
+ }
2337
+
2338
+ function widthAt(move) {
2339
+ var delta = move.pageX - startX;
2340
+
2341
+ return Math.max(1, startWidth + (west ? -delta : delta));
2342
+ }
2343
+
2344
+ function onMove(move) {
2345
+ col.css('width', widthAt(move) + 'px');
2346
+ resizeMove(col, widthAt(move));
2347
+ }
2348
+
2349
+ function onUp(up) {
2350
+ handle.off('pointermove', onMove).off('pointerup pointercancel', onUp);
2351
+ col.removeClass('ge-resizing');
2352
+ resizeStop(col, widthAt(up));
2353
+ }
2354
+
2355
+ handle.on('pointermove', onMove).on('pointerup pointercancel', onUp);
2356
+ }
2357
+
2135
2358
  function removeResizable() {
2136
- canvas.find('.column').each(function() {
2137
- var col = $(this);
2138
- if (col.data('ui-resizable')) { col.resizable('destroy'); }
2139
- });
2359
+ canvas.find('.ge-resize-handle').remove();
2140
2360
 
2141
2361
  canvas.find('.ge-resize-size').remove();
2142
2362
  stripPixelWidths(canvas);
@@ -2189,16 +2409,12 @@ $.fn.gridEditor = function( optionsOrMethod ) {
2189
2409
  }
2190
2410
 
2191
2411
  /**
2192
- * A canceled before-resize refuses the drag.
2193
- *
2194
- * jQuery UI's resizable ignores false from its start handler - unlike
2195
- * draggable, and unlike what the spec assumed - so the refusal is
2196
- * carried on the column and every step of the drag returns false,
2197
- * which the widget does honour. Nothing is written and the column ends
2198
- * where it began.
2199
- */
2200
- function resizeStart(e, ui) {
2201
- var col = $(this);
2412
+ * A canceled before-resize refuses the gesture before it begins, so
2413
+ * nothing is written and the column does not move. Under jQuery UI
2414
+ * this took a flag on the column and a refusal from every step of the
2415
+ * drag, because resizable ignores false from its start handler.
2416
+ */
2417
+ function resizeStart(col) {
2202
2418
  var from = currentSize(col);
2203
2419
 
2204
2420
  var allowed = operate(function() {
@@ -2209,40 +2425,27 @@ $.fn.gridEditor = function( optionsOrMethod ) {
2209
2425
  }));
2210
2426
  });
2211
2427
 
2212
- if (!allowed) {
2213
- col.data('ge-resize-refused', true);
2214
- return false;
2215
- }
2428
+ if (!allowed) { return false; }
2216
2429
 
2217
2430
  col.data('ge-resize-from', from);
2218
2431
  resizeReadout(col, sizeLabel(from));
2219
2432
 
2220
- return undefined;
2433
+ return true;
2221
2434
  }
2222
2435
 
2223
- function resizeMove(e, ui) {
2224
- var col = $(this);
2225
-
2226
- if (col.data('ge-resize-refused')) { return false; }
2227
-
2228
- resizeReadout(col, sizeLabel(snapUnits(col, ui.size.width)));
2229
-
2230
- return undefined;
2436
+ function resizeMove(col, width) {
2437
+ resizeReadout(col, sizeLabel(snapUnits(col, width)));
2231
2438
  }
2232
2439
 
2233
- function resizeStop(e, ui) {
2234
- var col = $(this);
2440
+ function resizeStop(col, width) {
2235
2441
  var from = col.data('ge-resize-from');
2236
- var units = snapUnits(col, ui.size.width);
2442
+ var units = snapUnits(col, width);
2237
2443
 
2238
2444
  col.removeData('ge-resize-from');
2239
2445
  resizeReadout(col, '');
2240
2446
  stripPixelWidths(col);
2241
2447
 
2242
- if (col.data('ge-resize-refused') || from === undefined) {
2243
- col.removeData('ge-resize-refused');
2244
- return;
2245
- }
2448
+ if (from === undefined) { return; }
2246
2449
 
2247
2450
  // A drag of a couple of pixels lands on the size it started from,
2248
2451
  // and is not a resize
@@ -2275,18 +2478,17 @@ $.fn.gridEditor = function( optionsOrMethod ) {
2275
2478
  if (plan) { writeSize(next, plan); }
2276
2479
  }
2277
2480
 
2481
+ /**
2482
+ * Undo every list `sortable()` made, and only those.
2483
+ *
2484
+ * The registry is what makes that exact: deinit() is a public method,
2485
+ * so it can be called twice, and a list the host made sortable itself
2486
+ * is none of the editor's business.
2487
+ */
2278
2488
  function removeSortable() {
2279
- // Only where a sortable was actually made: deinit() is a public
2280
- // method now, and jQuery UI throws when asked to destroy a widget
2281
- // that is not there, so calling deinit() twice would fail.
2282
- // jQuery UI marks what it made, so a plugin's sortables come
2283
- // away with the editor's without the core knowing about them
2284
- canvas.find('.ui-sortable').addBack('.ui-sortable').each(function() {
2285
- var node = $(this);
2286
- if (node.data('ui-sortable')) {
2287
- node.sortable('destroy');
2288
- }
2289
- });
2489
+ $.each(sortables, function(_, instance) { instance.destroy(); });
2490
+
2491
+ sortables = [];
2290
2492
  }
2291
2493
 
2292
2494
  function createRow() {
@@ -2476,10 +2678,10 @@ $.fn.gridEditor = function( optionsOrMethod ) {
2476
2678
  var child = $(this);
2477
2679
 
2478
2680
  // The editor's own furniture is not content and not a
2479
- // boundary either. jQuery UI's resize handle used to be
2480
- // treated as content and wrapped into a content area of
2481
- // its own on the next init.
2482
- if (child.is('.ge-tools-drawer, .ui-resizable-handle')) { return; }
2681
+ // boundary either. The resize handle used to be treated as
2682
+ // content and wrapped into a content area of its own on
2683
+ // the next init.
2684
+ if (child.is('.ge-tools-drawer, .ge-resize-handle')) { return; }
2483
2685
 
2484
2686
  // A container sits in the column beside the content
2485
2687
  // areas, not inside one, so it ends a run of loose
@@ -2702,6 +2904,8 @@ $.fn.gridEditor.locales = {
2702
2904
  'view.lg': 'Desktop',
2703
2905
  'view.xl': 'Large desktop',
2704
2906
  'view.xxl': 'Widescreen',
2907
+ 'error.sortable_missing': 'SortableJS not available! Make sure you loaded the Sortable js file; dragging is off without it.',
2908
+ 'warning.setting_removed': 'The {setting} setting was removed in 4.0. Use {replacement} instead.',
2705
2909
  'error.tinymce_missing': 'tinyMCE not available! Make sure you loaded the tinyMCE js file.',
2706
2910
  'error.ckeditor_missing': 'CKEditor not available! Make sure you loaded the ckeditor and jquery adapter js files.',
2707
2911
  'error.summernote_missing': 'Summernote not available! Make sure you loaded the Summernote js file.',