spina-admin-journal 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +99 -0
  4. data/Rakefile +36 -0
  5. data/app/assets/config/spina_admin_journal_manifest.js +3 -0
  6. data/app/assets/javascripts/spina/admin/journal/application.es6 +72 -0
  7. data/app/assets/stylesheets/spina/admin/journal/application.css +27 -0
  8. data/app/controllers/spina/admin/journal/application_controller.rb +26 -0
  9. data/app/controllers/spina/admin/journal/articles_controller.rb +125 -0
  10. data/app/controllers/spina/admin/journal/authors_controller.rb +83 -0
  11. data/app/controllers/spina/admin/journal/institutions_controller.rb +71 -0
  12. data/app/controllers/spina/admin/journal/issues_controller.rb +129 -0
  13. data/app/controllers/spina/admin/journal/journals_controller.rb +73 -0
  14. data/app/controllers/spina/admin/journal/volumes_controller.rb +79 -0
  15. data/app/models/spina/admin/journal.rb +11 -0
  16. data/app/models/spina/admin/journal/affiliation.rb +62 -0
  17. data/app/models/spina/admin/journal/article.rb +55 -0
  18. data/app/models/spina/admin/journal/author.rb +47 -0
  19. data/app/models/spina/admin/journal/authorship.rb +19 -0
  20. data/app/models/spina/admin/journal/institution.rb +32 -0
  21. data/app/models/spina/admin/journal/issue.rb +49 -0
  22. data/app/models/spina/admin/journal/journal.rb +50 -0
  23. data/app/models/spina/admin/journal/volume.rb +37 -0
  24. data/app/validators/spina/admin/journal/uri_validator.rb +24 -0
  25. data/app/views/layouts/spina/admin/journal/articles.html.haml +10 -0
  26. data/app/views/layouts/spina/admin/journal/authors.html.haml +10 -0
  27. data/app/views/layouts/spina/admin/journal/institutions.html.haml +10 -0
  28. data/app/views/layouts/spina/admin/journal/issues.html.haml +10 -0
  29. data/app/views/layouts/spina/admin/journal/journals.html.haml +10 -0
  30. data/app/views/layouts/spina/admin/journal/volumes.html.haml +10 -0
  31. data/app/views/spina/admin/hooks/journal/_head.html.haml +2 -0
  32. data/app/views/spina/admin/hooks/journal/_primary_navigation.html.haml +27 -0
  33. data/app/views/spina/admin/hooks/journal/_website_secondary_navigation.html.haml +0 -0
  34. data/app/views/spina/admin/journal/affiliations/_affiliation.html.haml +8 -0
  35. data/app/views/spina/admin/journal/application/_empty_list.html.haml +3 -0
  36. data/app/views/spina/admin/journal/articles/_article.html.haml +10 -0
  37. data/app/views/spina/admin/journal/articles/_form.html.haml +29 -0
  38. data/app/views/spina/admin/journal/articles/_form_authors.html.haml +10 -0
  39. data/app/views/spina/admin/journal/articles/_form_details.html.haml +52 -0
  40. data/app/views/spina/admin/journal/articles/edit.html.haml +1 -0
  41. data/app/views/spina/admin/journal/articles/index.html.haml +19 -0
  42. data/app/views/spina/admin/journal/articles/new.html.haml +1 -0
  43. data/app/views/spina/admin/journal/authors/_author.html.haml +8 -0
  44. data/app/views/spina/admin/journal/authors/_form.html.haml +29 -0
  45. data/app/views/spina/admin/journal/authors/_form_affiliation.html.haml +20 -0
  46. data/app/views/spina/admin/journal/authors/_form_articles.html.haml +18 -0
  47. data/app/views/spina/admin/journal/authors/_form_details.html.haml +6 -0
  48. data/app/views/spina/admin/journal/authors/edit.html.haml +1 -0
  49. data/app/views/spina/admin/journal/authors/index.html.haml +17 -0
  50. data/app/views/spina/admin/journal/authors/new.html.haml +1 -0
  51. data/app/views/spina/admin/journal/institutions/_form.html.haml +29 -0
  52. data/app/views/spina/admin/journal/institutions/_form_details.html.haml +9 -0
  53. data/app/views/spina/admin/journal/institutions/_form_view_affiliations.html.haml +10 -0
  54. data/app/views/spina/admin/journal/institutions/_institution.html.haml +9 -0
  55. data/app/views/spina/admin/journal/institutions/edit.html.haml +1 -0
  56. data/app/views/spina/admin/journal/institutions/index.html.haml +16 -0
  57. data/app/views/spina/admin/journal/institutions/new.html.haml +1 -0
  58. data/app/views/spina/admin/journal/issues/_form.html.haml +29 -0
  59. data/app/views/spina/admin/journal/issues/_form_articles.html.haml +14 -0
  60. data/app/views/spina/admin/journal/issues/_form_details.html.haml +32 -0
  61. data/app/views/spina/admin/journal/issues/_issue.html.haml +9 -0
  62. data/app/views/spina/admin/journal/issues/edit.html.haml +1 -0
  63. data/app/views/spina/admin/journal/issues/index.html.haml +18 -0
  64. data/app/views/spina/admin/journal/issues/new.html.haml +1 -0
  65. data/app/views/spina/admin/journal/journals/_form.html.haml +35 -0
  66. data/app/views/spina/admin/journal/journals/edit.html.haml +1 -0
  67. data/app/views/spina/admin/journal/journals/index.html.haml +27 -0
  68. data/app/views/spina/admin/journal/journals/new.html.haml +1 -0
  69. data/app/views/spina/admin/journal/volumes/_form.html.haml +15 -0
  70. data/app/views/spina/admin/journal/volumes/_form_details.html.haml +10 -0
  71. data/app/views/spina/admin/journal/volumes/_form_issues.html.haml +13 -0
  72. data/app/views/spina/admin/journal/volumes/_volume.html.haml +7 -0
  73. data/app/views/spina/admin/journal/volumes/edit.html.haml +1 -0
  74. data/app/views/spina/admin/journal/volumes/index.html.haml +17 -0
  75. data/app/views/spina/admin/journal/volumes/new.html.haml +1 -0
  76. data/config/locales/en.yml +184 -0
  77. data/config/routes.rb +21 -0
  78. data/db/migrate/20201216152147_create_spina_admin_journal_journals.rb +13 -0
  79. data/db/migrate/20201216153822_create_spina_admin_journal_volumes.rb +13 -0
  80. data/db/migrate/20201216155113_create_spina_admin_journal_issues.rb +15 -0
  81. data/db/migrate/20201216161122_create_spina_admin_journal_articles.rb +16 -0
  82. data/db/migrate/20201216205633_create_spina_admin_journal_institutions.rb +11 -0
  83. data/db/migrate/20201216211224_create_spina_admin_journal_authors.rb +7 -0
  84. data/db/migrate/20201216221146_create_spina_admin_journal_affiliations.rb +15 -0
  85. data/db/migrate/20201216230816_create_spina_admin_journal_authorships.rb +14 -0
  86. data/db/migrate/20201231165231_create_spina_admin_journal_parts.rb +12 -0
  87. data/db/migrate/20210424123450_add_json_attributes_to_spina_admin_journal_journals.rb +7 -0
  88. data/db/migrate/20210424123521_add_json_attributes_to_spina_admin_journal_issues.rb +7 -0
  89. data/db/migrate/20210424123555_add_json_attributes_to_spina_admin_journal_articles.rb +7 -0
  90. data/lib/spina/admin/journal.rb +18 -0
  91. data/lib/spina/admin/journal/engine.rb +17 -0
  92. data/lib/spina/admin/journal/version.rb +9 -0
  93. data/lib/tasks/spina/admin/journal_tasks.rake +5 -0
  94. data/vendor/assets/javascripts/spina/admin/journal/html5sortable.js +1295 -0
  95. metadata +388 -0
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddJsonAttributesToSpinaAdminJournalJournals < ActiveRecord::Migration[6.1] # :nodoc:
4
+ def change
5
+ add_column :spina_admin_journal_journals, :json_attributes, :jsonb
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddJsonAttributesToSpinaAdminJournalIssues < ActiveRecord::Migration[6.1] # :nodoc:
4
+ def change
5
+ add_column :spina_admin_journal_issues, :json_attributes, :jsonb
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddJsonAttributesToSpinaAdminJournalArticles < ActiveRecord::Migration[6.1] # :nodoc:
4
+ def change
5
+ add_column :spina_admin_journal_articles, :json_attributes, :jsonb
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spina'
4
+ require 'spina/admin/journal/engine'
5
+ require 'rails-i18n'
6
+
7
+ # Spina CMS.
8
+ # @see https://www.spinacms.com Spina website
9
+ module Spina
10
+ module Admin
11
+ # Journal management plugin for Spina.
12
+ module Journal
13
+ def self.table_name_prefix
14
+ 'spina_admin_journal_'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spina
4
+ module Admin
5
+ module Journal
6
+ # Registers the plugin with Spina.
7
+ class Engine < ::Rails::Engine
8
+ config.before_initialize do
9
+ ::Spina::Plugin.register do |plugin|
10
+ plugin.name = 'journal'
11
+ plugin.namespace = 'journal'
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spina
4
+ module Admin
5
+ module Journal
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # desc "Explaining what the task does"
3
+ # task :spina_admin_journal do
4
+ # # Task goes here
5
+ # end
@@ -0,0 +1,1295 @@
1
+ /*
2
+ * HTML5Sortable package
3
+ * https://github.com/lukasoppermann/html5sortable
4
+ *
5
+ * Maintained by Lukas Oppermann <lukas@vea.re>
6
+ *
7
+ * Released under the MIT license.
8
+ */
9
+ var sortable = (function () {
10
+ 'use strict';
11
+
12
+ /**
13
+ * Get or set data on element
14
+ * @param {HTMLElement} element
15
+ * @param {string} key
16
+ * @param {any} value
17
+ * @return {*}
18
+ */
19
+ function addData(element, key, value) {
20
+ if (value === undefined) {
21
+ return element && element.h5s && element.h5s.data && element.h5s.data[key];
22
+ }
23
+ else {
24
+ element.h5s = element.h5s || {};
25
+ element.h5s.data = element.h5s.data || {};
26
+ element.h5s.data[key] = value;
27
+ }
28
+ }
29
+ /**
30
+ * Remove data from element
31
+ * @param {HTMLElement} element
32
+ */
33
+ function removeData(element) {
34
+ if (element.h5s) {
35
+ delete element.h5s.data;
36
+ }
37
+ }
38
+
39
+ /* eslint-env browser */
40
+ /**
41
+ * Filter only wanted nodes
42
+ * @param {NodeList|HTMLCollection|Array} nodes
43
+ * @param {String} selector
44
+ * @returns {Array}
45
+ */
46
+ var _filter = (function (nodes, selector) {
47
+ if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) {
48
+ throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.');
49
+ }
50
+ if (typeof selector !== 'string') {
51
+ return Array.from(nodes);
52
+ }
53
+ return Array.from(nodes).filter(function (item) { return item.nodeType === 1 && item.matches(selector); });
54
+ });
55
+
56
+ /* eslint-env browser */
57
+ var stores = new Map();
58
+ /**
59
+ * Stores data & configurations per Sortable
60
+ * @param {Object} config
61
+ */
62
+ var Store = /** @class */ (function () {
63
+ function Store() {
64
+ this._config = new Map(); // eslint-disable-line no-undef
65
+ this._placeholder = undefined; // eslint-disable-line no-undef
66
+ this._data = new Map(); // eslint-disable-line no-undef
67
+ }
68
+ Object.defineProperty(Store.prototype, "config", {
69
+ /**
70
+ * get the configuration map of a class instance
71
+ * @method config
72
+ * @return {object}
73
+ */
74
+ get: function () {
75
+ // transform Map to object
76
+ var config = {};
77
+ this._config.forEach(function (value, key) {
78
+ config[key] = value;
79
+ });
80
+ // return object
81
+ return config;
82
+ },
83
+ /**
84
+ * set the configuration of a class instance
85
+ * @method config
86
+ * @param {object} config object of configurations
87
+ */
88
+ set: function (config) {
89
+ if (typeof config !== 'object') {
90
+ throw new Error('You must provide a valid configuration object to the config setter.');
91
+ }
92
+ // combine config with default
93
+ var mergedConfig = Object.assign({}, config);
94
+ // add config to map
95
+ this._config = new Map(Object.entries(mergedConfig));
96
+ },
97
+ enumerable: false,
98
+ configurable: true
99
+ });
100
+ /**
101
+ * set individual configuration of a class instance
102
+ * @method setConfig
103
+ * @param key valid configuration key
104
+ * @param value any value
105
+ * @return void
106
+ */
107
+ Store.prototype.setConfig = function (key, value) {
108
+ if (!this._config.has(key)) {
109
+ throw new Error("Trying to set invalid configuration item: " + key);
110
+ }
111
+ // set config
112
+ this._config.set(key, value);
113
+ };
114
+ /**
115
+ * get an individual configuration of a class instance
116
+ * @method getConfig
117
+ * @param key valid configuration key
118
+ * @return any configuration value
119
+ */
120
+ Store.prototype.getConfig = function (key) {
121
+ if (!this._config.has(key)) {
122
+ throw new Error("Invalid configuration item requested: " + key);
123
+ }
124
+ return this._config.get(key);
125
+ };
126
+ Object.defineProperty(Store.prototype, "placeholder", {
127
+ /**
128
+ * get the placeholder for a class instance
129
+ * @method placeholder
130
+ * @return {HTMLElement|null}
131
+ */
132
+ get: function () {
133
+ return this._placeholder;
134
+ },
135
+ /**
136
+ * set the placeholder for a class instance
137
+ * @method placeholder
138
+ * @param {HTMLElement} placeholder
139
+ * @return {void}
140
+ */
141
+ set: function (placeholder) {
142
+ if (!(placeholder instanceof HTMLElement) && placeholder !== null) {
143
+ throw new Error('A placeholder must be an html element or null.');
144
+ }
145
+ this._placeholder = placeholder;
146
+ },
147
+ enumerable: false,
148
+ configurable: true
149
+ });
150
+ /**
151
+ * set an data entry
152
+ * @method setData
153
+ * @param {string} key
154
+ * @param {any} value
155
+ * @return {void}
156
+ */
157
+ Store.prototype.setData = function (key, value) {
158
+ if (typeof key !== 'string') {
159
+ throw new Error('The key must be a string.');
160
+ }
161
+ this._data.set(key, value);
162
+ };
163
+ /**
164
+ * get an data entry
165
+ * @method getData
166
+ * @param {string} key an existing key
167
+ * @return {any}
168
+ */
169
+ Store.prototype.getData = function (key) {
170
+ if (typeof key !== 'string') {
171
+ throw new Error('The key must be a string.');
172
+ }
173
+ return this._data.get(key);
174
+ };
175
+ /**
176
+ * delete an data entry
177
+ * @method deleteData
178
+ * @param {string} key an existing key
179
+ * @return {boolean}
180
+ */
181
+ Store.prototype.deleteData = function (key) {
182
+ if (typeof key !== 'string') {
183
+ throw new Error('The key must be a string.');
184
+ }
185
+ return this._data.delete(key);
186
+ };
187
+ return Store;
188
+ }());
189
+ /**
190
+ * @param {HTMLElement} sortableElement
191
+ * @returns {Class: Store}
192
+ */
193
+ var store = (function (sortableElement) {
194
+ // if sortableElement is wrong type
195
+ if (!(sortableElement instanceof HTMLElement)) {
196
+ throw new Error('Please provide a sortable to the store function.');
197
+ }
198
+ // create new instance if not avilable
199
+ if (!stores.has(sortableElement)) {
200
+ stores.set(sortableElement, new Store());
201
+ }
202
+ // return instance
203
+ return stores.get(sortableElement);
204
+ });
205
+
206
+ /**
207
+ * @param {Array|HTMLElement} element
208
+ * @param {Function} callback
209
+ * @param {string} event
210
+ */
211
+ function addEventListener(element, eventName, callback) {
212
+ if (element instanceof Array) {
213
+ for (var i = 0; i < element.length; ++i) {
214
+ addEventListener(element[i], eventName, callback);
215
+ }
216
+ return;
217
+ }
218
+ element.addEventListener(eventName, callback);
219
+ store(element).setData("event" + eventName, callback);
220
+ }
221
+ /**
222
+ * @param {Array<HTMLElement>|HTMLElement} element
223
+ * @param {string} eventName
224
+ */
225
+ function removeEventListener(element, eventName) {
226
+ if (element instanceof Array) {
227
+ for (var i = 0; i < element.length; ++i) {
228
+ removeEventListener(element[i], eventName);
229
+ }
230
+ return;
231
+ }
232
+ element.removeEventListener(eventName, store(element).getData("event" + eventName));
233
+ store(element).deleteData("event" + eventName);
234
+ }
235
+
236
+ /**
237
+ * @param {Array<HTMLElement>|HTMLElement} element
238
+ * @param {string} attribute
239
+ * @param {string} value
240
+ */
241
+ function addAttribute(element, attribute, value) {
242
+ if (element instanceof Array) {
243
+ for (var i = 0; i < element.length; ++i) {
244
+ addAttribute(element[i], attribute, value);
245
+ }
246
+ return;
247
+ }
248
+ element.setAttribute(attribute, value);
249
+ }
250
+ /**
251
+ * @param {Array|HTMLElement} element
252
+ * @param {string} attribute
253
+ */
254
+ function removeAttribute(element, attribute) {
255
+ if (element instanceof Array) {
256
+ for (var i = 0; i < element.length; ++i) {
257
+ removeAttribute(element[i], attribute);
258
+ }
259
+ return;
260
+ }
261
+ element.removeAttribute(attribute);
262
+ }
263
+
264
+ /**
265
+ * @param {HTMLElement} element
266
+ * @returns {Object}
267
+ */
268
+ var _offset = (function (element) {
269
+ if (!element.parentElement || element.getClientRects().length === 0) {
270
+ throw new Error('target element must be part of the dom');
271
+ }
272
+ var rect = element.getClientRects()[0];
273
+ return {
274
+ left: rect.left + window.pageXOffset,
275
+ right: rect.right + window.pageXOffset,
276
+ top: rect.top + window.pageYOffset,
277
+ bottom: rect.bottom + window.pageYOffset
278
+ };
279
+ });
280
+
281
+ /**
282
+ * Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed
283
+ * @param {Function} func to debounce
284
+ * @param {number} time to wait before calling function with latest arguments, 0 - no debounce
285
+ * @returns {function} - debounced function
286
+ */
287
+ var _debounce = (function (func, wait) {
288
+ if (wait === void 0) { wait = 0; }
289
+ var timeout;
290
+ return function () {
291
+ var args = [];
292
+ for (var _i = 0; _i < arguments.length; _i++) {
293
+ args[_i] = arguments[_i];
294
+ }
295
+ clearTimeout(timeout);
296
+ timeout = setTimeout(function () {
297
+ func.apply(void 0, args);
298
+ }, wait);
299
+ };
300
+ });
301
+
302
+ /* eslint-env browser */
303
+ /**
304
+ * Get position of the element relatively to its sibling elements
305
+ * @param {HTMLElement} element
306
+ * @returns {number}
307
+ */
308
+ var _index = (function (element, elementList) {
309
+ if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) {
310
+ throw new Error('You must provide an element and a list of elements.');
311
+ }
312
+ return Array.from(elementList).indexOf(element);
313
+ });
314
+
315
+ /* eslint-env browser */
316
+ /**
317
+ * Test whether element is in DOM
318
+ * @param {HTMLElement} element
319
+ * @returns {boolean}
320
+ */
321
+ var isInDom = (function (element) {
322
+ if (!(element instanceof HTMLElement)) {
323
+ throw new Error('Element is not a node element.');
324
+ }
325
+ return element.parentNode !== null;
326
+ });
327
+
328
+ /* eslint-env browser */
329
+ /**
330
+ * Insert node before or after target
331
+ * @param {HTMLElement} referenceNode - reference element
332
+ * @param {HTMLElement} newElement - element to be inserted
333
+ * @param {String} position - insert before or after reference element
334
+ */
335
+ var insertNode = function (referenceNode, newElement, position) {
336
+ if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) {
337
+ throw new Error('target and element must be a node');
338
+ }
339
+ referenceNode.parentElement.insertBefore(newElement, (position === 'before' ? referenceNode : referenceNode.nextElementSibling));
340
+ };
341
+ /**
342
+ * Insert before target
343
+ * @param {HTMLElement} target
344
+ * @param {HTMLElement} element
345
+ */
346
+ var insertBefore = function (target, element) { return insertNode(target, element, 'before'); };
347
+ /**
348
+ * Insert after target
349
+ * @param {HTMLElement} target
350
+ * @param {HTMLElement} element
351
+ */
352
+ var insertAfter = function (target, element) { return insertNode(target, element, 'after'); };
353
+
354
+ /* eslint-env browser */
355
+ /**
356
+ * Filter only wanted nodes
357
+ * @param {HTMLElement} sortableContainer
358
+ * @param {Function} customSerializer
359
+ * @returns {Array}
360
+ */
361
+ var _serialize = (function (sortableContainer, customItemSerializer, customContainerSerializer) {
362
+ if (customItemSerializer === void 0) { customItemSerializer = function (serializedItem, sortableContainer) { return serializedItem; }; }
363
+ if (customContainerSerializer === void 0) { customContainerSerializer = function (serializedContainer) { return serializedContainer; }; }
364
+ // check for valid sortableContainer
365
+ if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) {
366
+ throw new Error('You need to provide a sortableContainer to be serialized.');
367
+ }
368
+ // check for valid serializers
369
+ if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') {
370
+ throw new Error('You need to provide a valid serializer for items and the container.');
371
+ }
372
+ // get options
373
+ var options = addData(sortableContainer, 'opts');
374
+ var item = options.items;
375
+ // serialize container
376
+ var items = _filter(sortableContainer.children, item);
377
+ var serializedItems = items.map(function (item) {
378
+ return {
379
+ parent: sortableContainer,
380
+ node: item,
381
+ html: item.outerHTML,
382
+ index: _index(item, items)
383
+ };
384
+ });
385
+ // serialize container
386
+ var container = {
387
+ node: sortableContainer,
388
+ itemCount: serializedItems.length
389
+ };
390
+ return {
391
+ container: customContainerSerializer(container),
392
+ items: serializedItems.map(function (item) { return customItemSerializer(item, sortableContainer); })
393
+ };
394
+ });
395
+
396
+ /* eslint-env browser */
397
+ /**
398
+ * create a placeholder element
399
+ * @param {HTMLElement} sortableElement a single sortable
400
+ * @param {string|undefined} placeholder a string representing an html element
401
+ * @param {string} placeholderClasses a string representing the classes that should be added to the placeholder
402
+ */
403
+ var _makePlaceholder = (function (sortableElement, placeholder, placeholderClass) {
404
+ var _a;
405
+ if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; }
406
+ if (!(sortableElement instanceof HTMLElement)) {
407
+ throw new Error('You must provide a valid element as a sortable.');
408
+ }
409
+ // if placeholder is not an element
410
+ if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) {
411
+ throw new Error('You must provide a valid element as a placeholder or set ot to undefined.');
412
+ }
413
+ // if no placeholder element is given
414
+ if (placeholder === undefined) {
415
+ if (['UL', 'OL'].includes(sortableElement.tagName)) {
416
+ placeholder = document.createElement('li');
417
+ }
418
+ else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) {
419
+ placeholder = document.createElement('tr');
420
+ // set colspan to always all rows, otherwise the item can only be dropped in first column
421
+ placeholder.innerHTML = '<td colspan="100"></td>';
422
+ }
423
+ else {
424
+ placeholder = document.createElement('div');
425
+ }
426
+ }
427
+ // add classes to placeholder
428
+ if (typeof placeholderClass === 'string') {
429
+ (_a = placeholder.classList).add.apply(_a, placeholderClass.split(' '));
430
+ }
431
+ return placeholder;
432
+ });
433
+
434
+ /* eslint-env browser */
435
+ /**
436
+ * Get height of an element including padding
437
+ * @param {HTMLElement} element an dom element
438
+ */
439
+ var _getElementHeight = (function (element) {
440
+ if (!(element instanceof HTMLElement)) {
441
+ throw new Error('You must provide a valid dom element');
442
+ }
443
+ // get calculated style of element
444
+ var style = window.getComputedStyle(element);
445
+ // get only height if element has box-sizing: border-box specified
446
+ if (style.getPropertyValue('box-sizing') === 'border-box') {
447
+ return parseInt(style.getPropertyValue('height'), 10);
448
+ }
449
+ // pick applicable properties, convert to int and reduce by adding
450
+ return ['height', 'padding-top', 'padding-bottom']
451
+ .map(function (key) {
452
+ var int = parseInt(style.getPropertyValue(key), 10);
453
+ return isNaN(int) ? 0 : int;
454
+ })
455
+ .reduce(function (sum, value) { return sum + value; });
456
+ });
457
+
458
+ /* eslint-env browser */
459
+ /**
460
+ * Get width of an element including padding
461
+ * @param {HTMLElement} element an dom element
462
+ */
463
+ var _getElementWidth = (function (element) {
464
+ if (!(element instanceof HTMLElement)) {
465
+ throw new Error('You must provide a valid dom element');
466
+ }
467
+ // get calculated style of element
468
+ var style = window.getComputedStyle(element);
469
+ // pick applicable properties, convert to int and reduce by adding
470
+ return ['width', 'padding-left', 'padding-right']
471
+ .map(function (key) {
472
+ var int = parseInt(style.getPropertyValue(key), 10);
473
+ return isNaN(int) ? 0 : int;
474
+ })
475
+ .reduce(function (sum, value) { return sum + value; });
476
+ });
477
+
478
+ /* eslint-env browser */
479
+ /**
480
+ * get handle or return item
481
+ * @param {Array<HTMLElement>} items
482
+ * @param {string} selector
483
+ */
484
+ var _getHandles = (function (items, selector) {
485
+ if (!(items instanceof Array)) {
486
+ throw new Error('You must provide a Array of HTMLElements to be filtered.');
487
+ }
488
+ if (typeof selector !== 'string') {
489
+ return items;
490
+ }
491
+ return items
492
+ // remove items without handle from array
493
+ .filter(function (item) {
494
+ return item.querySelector(selector) instanceof HTMLElement ||
495
+ (item.shadowRoot && item.shadowRoot.querySelector(selector) instanceof HTMLElement);
496
+ })
497
+ // replace item with handle in array
498
+ .map(function (item) {
499
+ return item.querySelector(selector) || (item.shadowRoot && item.shadowRoot.querySelector(selector));
500
+ });
501
+ });
502
+
503
+ /**
504
+ * @param {Event} event
505
+ * @returns {HTMLElement}
506
+ */
507
+ var getEventTarget = (function (event) {
508
+ return (event.composedPath && event.composedPath()[0]) || event.target;
509
+ });
510
+
511
+ /* eslint-env browser */
512
+ /**
513
+ * defaultDragImage returns the current item as dragged image
514
+ * @param {HTMLElement} draggedElement - the item that the user drags
515
+ * @param {object} elementOffset - an object with the offsets top, left, right & bottom
516
+ * @param {Event} event - the original drag event object
517
+ * @return {object} with element, posX and posY properties
518
+ */
519
+ var defaultDragImage = function (draggedElement, elementOffset, event) {
520
+ return {
521
+ element: draggedElement,
522
+ posX: event.pageX - elementOffset.left,
523
+ posY: event.pageY - elementOffset.top
524
+ };
525
+ };
526
+ /**
527
+ * attaches an element as the drag image to an event
528
+ * @param {Event} event - the original drag event object
529
+ * @param {HTMLElement} draggedElement - the item that the user drags
530
+ * @param {Function} customDragImage - function to create a custom dragImage
531
+ * @return void
532
+ */
533
+ var setDragImage = (function (event, draggedElement, customDragImage) {
534
+ // check if event is provided
535
+ if (!(event instanceof Event)) {
536
+ throw new Error('setDragImage requires a DragEvent as the first argument.');
537
+ }
538
+ // check if draggedElement is provided
539
+ if (!(draggedElement instanceof HTMLElement)) {
540
+ throw new Error('setDragImage requires the dragged element as the second argument.');
541
+ }
542
+ // set default function of none provided
543
+ if (!customDragImage) {
544
+ customDragImage = defaultDragImage;
545
+ }
546
+ // check if setDragImage method is available
547
+ if (event.dataTransfer && event.dataTransfer.setDragImage) {
548
+ // get the elements offset
549
+ var elementOffset = _offset(draggedElement);
550
+ // get the dragImage
551
+ var dragImage = customDragImage(draggedElement, elementOffset, event);
552
+ // check if custom function returns correct values
553
+ if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') {
554
+ throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].');
555
+ }
556
+ // needs to be set for HTML5 drag & drop to work
557
+ event.dataTransfer.effectAllowed = 'copyMove';
558
+ // Firefox requires it to use the event target's id for the data
559
+ event.dataTransfer.setData('text/plain', getEventTarget(event).id);
560
+ // set the drag image on the event
561
+ event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY);
562
+ }
563
+ });
564
+
565
+ /**
566
+ * Check if curList accepts items from destList
567
+ * @param {sortable} destination the container an item is move to
568
+ * @param {sortable} origin the container an item comes from
569
+ */
570
+ var _listsConnected = (function (destination, origin) {
571
+ // check if valid sortable
572
+ if (destination.isSortable === true) {
573
+ var acceptFrom = store(destination).getConfig('acceptFrom');
574
+ // check if acceptFrom is valid
575
+ if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') {
576
+ throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.');
577
+ }
578
+ if (acceptFrom !== null) {
579
+ return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) {
580
+ return sel.length > 0 && origin.matches(sel);
581
+ }).length > 0;
582
+ }
583
+ // drop in same list
584
+ if (destination === origin) {
585
+ return true;
586
+ }
587
+ // check if lists are connected with connectWith
588
+ if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) {
589
+ return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith');
590
+ }
591
+ }
592
+ return false;
593
+ });
594
+
595
+ /**
596
+ * default configurations
597
+ */
598
+ var defaultConfiguration = {
599
+ items: null,
600
+ // deprecated
601
+ connectWith: null,
602
+ // deprecated
603
+ disableIEFix: null,
604
+ acceptFrom: null,
605
+ copy: false,
606
+ placeholder: null,
607
+ placeholderClass: 'sortable-placeholder',
608
+ draggingClass: 'sortable-dragging',
609
+ hoverClass: false,
610
+ dropTargetContainerClass: false,
611
+ debounce: 0,
612
+ throttleTime: 100,
613
+ maxItems: 0,
614
+ itemSerializer: undefined,
615
+ containerSerializer: undefined,
616
+ customDragImage: null,
617
+ orientation: 'vertical'
618
+ };
619
+
620
+ /**
621
+ * make sure a function is only called once within the given amount of time
622
+ * @param {Function} fn the function to throttle
623
+ * @param {number} threshold time limit for throttling
624
+ */
625
+ // must use function to keep this context
626
+ function _throttle (fn, threshold) {
627
+ var _this = this;
628
+ if (threshold === void 0) { threshold = 250; }
629
+ // check function
630
+ if (typeof fn !== 'function') {
631
+ throw new Error('You must provide a function as the first argument for throttle.');
632
+ }
633
+ // check threshold
634
+ if (typeof threshold !== 'number') {
635
+ throw new Error('You must provide a number as the second argument for throttle.');
636
+ }
637
+ var lastEventTimestamp = null;
638
+ return function () {
639
+ var args = [];
640
+ for (var _i = 0; _i < arguments.length; _i++) {
641
+ args[_i] = arguments[_i];
642
+ }
643
+ var now = Date.now();
644
+ if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) {
645
+ lastEventTimestamp = now;
646
+ fn.apply(_this, args);
647
+ }
648
+ };
649
+ }
650
+
651
+ /* eslint-env browser */
652
+ /**
653
+ * enable or disable hoverClass on mouseenter/leave if container Items
654
+ * @param {sortable} sortableContainer a valid sortableContainer
655
+ * @param {boolean} enable enable or disable event
656
+ */
657
+ // export default (sortableContainer: sortable, enable: boolean) => {
658
+ var enableHoverClass = (function (sortableContainer, enable) {
659
+ if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') {
660
+ var hoverClasses_1 = store(sortableContainer).getConfig('hoverClass').split(' ');
661
+ // add class on hover
662
+ if (enable === true) {
663
+ addEventListener(sortableContainer, 'mousemove', _throttle(function (event) {
664
+ // check of no mouse button was pressed when mousemove started == no drag
665
+ if (event.buttons === 0) {
666
+ _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) {
667
+ var _a, _b;
668
+ if (item !== event.target) {
669
+ (_a = item.classList).remove.apply(_a, hoverClasses_1);
670
+ }
671
+ else {
672
+ (_b = item.classList).add.apply(_b, hoverClasses_1);
673
+ }
674
+ });
675
+ }
676
+ }, store(sortableContainer).getConfig('throttleTime')));
677
+ // remove class on leave
678
+ addEventListener(sortableContainer, 'mouseleave', function () {
679
+ _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) {
680
+ var _a;
681
+ (_a = item.classList).remove.apply(_a, hoverClasses_1);
682
+ });
683
+ });
684
+ // remove events
685
+ }
686
+ else {
687
+ removeEventListener(sortableContainer, 'mousemove');
688
+ removeEventListener(sortableContainer, 'mouseleave');
689
+ }
690
+ }
691
+ });
692
+
693
+ /* eslint-env browser */
694
+ /*
695
+ * variables global to the plugin
696
+ */
697
+ var dragging;
698
+ var draggingHeight;
699
+ var draggingWidth;
700
+ /*
701
+ * Keeps track of the initialy selected list, where 'dragstart' event was triggered
702
+ * It allows us to move the data in between individual Sortable List instances
703
+ */
704
+ // Origin List - data from before any item was changed
705
+ var originContainer;
706
+ var originIndex;
707
+ var originElementIndex;
708
+ var originItemsBeforeUpdate;
709
+ // Previous Sortable Container - we dispatch as sortenter event when a
710
+ // dragged item enters a sortableContainer for the first time
711
+ var previousContainer;
712
+ // Destination List - data from before any item was changed
713
+ var destinationItemsBeforeUpdate;
714
+ /**
715
+ * remove event handlers from items
716
+ * @param {Array|NodeList} items
717
+ */
718
+ var _removeItemEvents = function (items) {
719
+ removeEventListener(items, 'dragstart');
720
+ removeEventListener(items, 'dragend');
721
+ removeEventListener(items, 'dragover');
722
+ removeEventListener(items, 'dragenter');
723
+ removeEventListener(items, 'drop');
724
+ removeEventListener(items, 'mouseenter');
725
+ removeEventListener(items, 'mouseleave');
726
+ };
727
+ // Remove container events
728
+ var _removeContainerEvents = function (originContainer, previousContainer) {
729
+ if (originContainer) {
730
+ removeEventListener(originContainer, 'dragleave');
731
+ }
732
+ if (previousContainer && (previousContainer !== originContainer)) {
733
+ removeEventListener(previousContainer, 'dragleave');
734
+ }
735
+ };
736
+ /**
737
+ * _getDragging returns the current element to drag or
738
+ * a copy of the element.
739
+ * Is Copy Active for sortable
740
+ * @param {HTMLElement} draggedItem - the item that the user drags
741
+ * @param {HTMLElement} sortable a single sortable
742
+ */
743
+ var _getDragging = function (draggedItem, sortable) {
744
+ var ditem = draggedItem;
745
+ if (store(sortable).getConfig('copy') === true) {
746
+ ditem = draggedItem.cloneNode(true);
747
+ addAttribute(ditem, 'aria-copied', 'true');
748
+ draggedItem.parentElement.appendChild(ditem);
749
+ ditem.style.display = 'none';
750
+ ditem.oldDisplay = draggedItem.style.display;
751
+ }
752
+ return ditem;
753
+ };
754
+ /**
755
+ * Remove data from sortable
756
+ * @param {HTMLElement} sortable a single sortable
757
+ */
758
+ var _removeSortableData = function (sortable) {
759
+ removeData(sortable);
760
+ removeAttribute(sortable, 'aria-dropeffect');
761
+ };
762
+ /**
763
+ * Remove data from items
764
+ * @param {Array<HTMLElement>|HTMLElement} items
765
+ */
766
+ var _removeItemData = function (items) {
767
+ removeAttribute(items, 'aria-grabbed');
768
+ removeAttribute(items, 'aria-copied');
769
+ removeAttribute(items, 'draggable');
770
+ removeAttribute(items, 'role');
771
+ };
772
+ /**
773
+ * find sortable from element. travels up parent element until found or null.
774
+ * @param {HTMLElement} element a single sortable
775
+ * @param {Event} event - the current event. We need to pass it to be able to
776
+ * find Sortable whith shadowRoot (document fragment has no parent)
777
+ */
778
+ function findSortable(element, event) {
779
+ if (event.composedPath) {
780
+ return event.composedPath().find(function (el) { return el.isSortable; });
781
+ }
782
+ while (element.isSortable !== true) {
783
+ element = element.parentElement;
784
+ }
785
+ return element;
786
+ }
787
+ /**
788
+ * Dragging event is on the sortable element. finds the top child that
789
+ * contains the element.
790
+ * @param {HTMLElement} sortableElement a single sortable
791
+ * @param {HTMLElement} element is that being dragged
792
+ */
793
+ function findDragElement(sortableElement, element) {
794
+ var options = addData(sortableElement, 'opts');
795
+ var items = _filter(sortableElement.children, options.items);
796
+ var itemlist = items.filter(function (ele) {
797
+ return ele.contains(element) || (ele.shadowRoot && ele.shadowRoot.contains(element));
798
+ });
799
+ return itemlist.length > 0 ? itemlist[0] : element;
800
+ }
801
+ /**
802
+ * Destroy the sortable
803
+ * @param {HTMLElement} sortableElement a single sortable
804
+ */
805
+ var _destroySortable = function (sortableElement) {
806
+ var opts = addData(sortableElement, 'opts') || {};
807
+ var items = _filter(sortableElement.children, opts.items);
808
+ var handles = _getHandles(items, opts.handle);
809
+ // remove event handlers & data from sortable
810
+ removeEventListener(sortableElement, 'dragover');
811
+ removeEventListener(sortableElement, 'dragenter');
812
+ removeEventListener(sortableElement, 'dragstart');
813
+ removeEventListener(sortableElement, 'dragend');
814
+ removeEventListener(sortableElement, 'drop');
815
+ // remove event data from sortable
816
+ _removeSortableData(sortableElement);
817
+ // remove event handlers & data from items
818
+ removeEventListener(handles, 'mousedown');
819
+ _removeItemEvents(items);
820
+ _removeItemData(items);
821
+ _removeContainerEvents(originContainer, previousContainer);
822
+ // clear sortable flag
823
+ sortableElement.isSortable = false;
824
+ };
825
+ /**
826
+ * Enable the sortable
827
+ * @param {HTMLElement} sortableElement a single sortable
828
+ */
829
+ var _enableSortable = function (sortableElement) {
830
+ var opts = addData(sortableElement, 'opts');
831
+ var items = _filter(sortableElement.children, opts.items);
832
+ var handles = _getHandles(items, opts.handle);
833
+ addAttribute(sortableElement, 'aria-dropeffect', 'move');
834
+ addData(sortableElement, '_disabled', 'false');
835
+ addAttribute(handles, 'draggable', 'true');
836
+ // @todo: remove this fix
837
+ // IE FIX for ghost
838
+ // can be disabled as it has the side effect that other events
839
+ // (e.g. click) will be ignored
840
+ if (opts.disableIEFix === false) {
841
+ var spanEl = (document || window.document).createElement('span');
842
+ if (typeof spanEl.dragDrop === 'function') {
843
+ addEventListener(handles, 'mousedown', function () {
844
+ if (items.indexOf(this) !== -1) {
845
+ this.dragDrop();
846
+ }
847
+ else {
848
+ var parent = this.parentElement;
849
+ while (items.indexOf(parent) === -1) {
850
+ parent = parent.parentElement;
851
+ }
852
+ parent.dragDrop();
853
+ }
854
+ });
855
+ }
856
+ }
857
+ };
858
+ /**
859
+ * Disable the sortable
860
+ * @param {HTMLElement} sortableElement a single sortable
861
+ */
862
+ var _disableSortable = function (sortableElement) {
863
+ var opts = addData(sortableElement, 'opts');
864
+ var items = _filter(sortableElement.children, opts.items);
865
+ var handles = _getHandles(items, opts.handle);
866
+ addAttribute(sortableElement, 'aria-dropeffect', 'none');
867
+ addData(sortableElement, '_disabled', 'true');
868
+ addAttribute(handles, 'draggable', 'false');
869
+ removeEventListener(handles, 'mousedown');
870
+ };
871
+ /**
872
+ * Reload the sortable
873
+ * @param {HTMLElement} sortableElement a single sortable
874
+ * @description events need to be removed to not be double bound
875
+ */
876
+ var _reloadSortable = function (sortableElement) {
877
+ var opts = addData(sortableElement, 'opts');
878
+ var items = _filter(sortableElement.children, opts.items);
879
+ var handles = _getHandles(items, opts.handle);
880
+ addData(sortableElement, '_disabled', 'false');
881
+ // remove event handlers from items
882
+ _removeItemEvents(items);
883
+ _removeContainerEvents();
884
+ removeEventListener(handles, 'mousedown');
885
+ // remove event handlers from sortable
886
+ removeEventListener(sortableElement, 'dragover');
887
+ removeEventListener(sortableElement, 'dragenter');
888
+ removeEventListener(sortableElement, 'drop');
889
+ };
890
+ /**
891
+ * Public sortable object
892
+ * @param {Array|NodeList} sortableElements
893
+ * @param {object|string} options|method
894
+ */
895
+ function sortable(sortableElements, options) {
896
+ // get method string to see if a method is called
897
+ var method = String(options);
898
+ options = options || {};
899
+ // check if the user provided a selector instead of an element
900
+ if (typeof sortableElements === 'string') {
901
+ sortableElements = document.querySelectorAll(sortableElements);
902
+ }
903
+ // if the user provided an element, return it in an array to keep the return value consistant
904
+ if (sortableElements instanceof HTMLElement) {
905
+ sortableElements = [sortableElements];
906
+ }
907
+ sortableElements = Array.prototype.slice.call(sortableElements);
908
+ if (/serialize/.test(method)) {
909
+ return sortableElements.map(function (sortableContainer) {
910
+ var opts = addData(sortableContainer, 'opts');
911
+ return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer);
912
+ });
913
+ }
914
+ sortableElements.forEach(function (sortableElement) {
915
+ if (/enable|disable|destroy/.test(method)) {
916
+ return sortable[method](sortableElement);
917
+ }
918
+ // log deprecation
919
+ ['connectWith', 'disableIEFix'].forEach(function (configKey) {
920
+ if (Object.prototype.hasOwnProperty.call(options, configKey) && options[configKey] !== null) {
921
+ console.warn("HTML5Sortable: You are using the deprecated configuration \"" + configKey + "\". This will be removed in an upcoming version, make sure to migrate to the new options when updating.");
922
+ }
923
+ });
924
+ // merge options with default options
925
+ options = Object.assign({}, defaultConfiguration, store(sortableElement).config, options);
926
+ // init data store for sortable
927
+ store(sortableElement).config = options;
928
+ // set options on sortable
929
+ addData(sortableElement, 'opts', options);
930
+ // property to define as sortable
931
+ sortableElement.isSortable = true;
932
+ // reset sortable
933
+ _reloadSortable(sortableElement);
934
+ // initialize
935
+ var listItems = _filter(sortableElement.children, options.items);
936
+ // create element if user defined a placeholder element as a string
937
+ var customPlaceholder;
938
+ if (options.placeholder !== null && options.placeholder !== undefined) {
939
+ var tempContainer = document.createElement(sortableElement.tagName);
940
+ if (options.placeholder instanceof HTMLElement) {
941
+ tempContainer.appendChild(options.placeholder);
942
+ }
943
+ else {
944
+ tempContainer.innerHTML = options.placeholder;
945
+ }
946
+ customPlaceholder = tempContainer.children[0];
947
+ }
948
+ // add placeholder
949
+ store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass);
950
+ addData(sortableElement, 'items', options.items);
951
+ if (options.acceptFrom) {
952
+ addData(sortableElement, 'acceptFrom', options.acceptFrom);
953
+ }
954
+ else if (options.connectWith) {
955
+ addData(sortableElement, 'connectWith', options.connectWith);
956
+ }
957
+ _enableSortable(sortableElement);
958
+ addAttribute(listItems, 'role', 'option');
959
+ addAttribute(listItems, 'aria-grabbed', 'false');
960
+ // enable hover class
961
+ enableHoverClass(sortableElement, true);
962
+ /*
963
+ Handle drag events on draggable items
964
+ Handle is set at the sortableElement level as it will bubble up
965
+ from the item
966
+ */
967
+ addEventListener(sortableElement, 'dragstart', function (e) {
968
+ // ignore dragstart events
969
+ var target = getEventTarget(e);
970
+ if (target.isSortable === true) {
971
+ return;
972
+ }
973
+ e.stopImmediatePropagation();
974
+ if ((options.handle && !target.matches(options.handle)) || target.getAttribute('draggable') === 'false') {
975
+ return;
976
+ }
977
+ var sortableContainer = findSortable(target, e);
978
+ var dragItem = findDragElement(sortableContainer, target);
979
+ // grab values
980
+ originItemsBeforeUpdate = _filter(sortableContainer.children, options.items);
981
+ originIndex = originItemsBeforeUpdate.indexOf(dragItem);
982
+ originElementIndex = _index(dragItem, sortableContainer.children);
983
+ originContainer = sortableContainer;
984
+ // add transparent clone or other ghost to cursor
985
+ setDragImage(e, dragItem, options.customDragImage);
986
+ // cache selsection & add attr for dragging
987
+ draggingHeight = _getElementHeight(dragItem);
988
+ draggingWidth = _getElementWidth(dragItem);
989
+ dragItem.classList.add(options.draggingClass);
990
+ dragging = _getDragging(dragItem, sortableContainer);
991
+ addAttribute(dragging, 'aria-grabbed', 'true');
992
+ // dispatch sortstart event on each element in group
993
+ sortableContainer.dispatchEvent(new CustomEvent('sortstart', {
994
+ detail: {
995
+ origin: {
996
+ elementIndex: originElementIndex,
997
+ index: originIndex,
998
+ container: originContainer
999
+ },
1000
+ item: dragging,
1001
+ originalTarget: target
1002
+ }
1003
+ }));
1004
+ });
1005
+ /*
1006
+ We are capturing targetSortable before modifications with 'dragenter' event
1007
+ */
1008
+ addEventListener(sortableElement, 'dragenter', function (e) {
1009
+ var target = getEventTarget(e);
1010
+ var sortableContainer = findSortable(target, e);
1011
+ if (sortableContainer && sortableContainer !== previousContainer) {
1012
+ destinationItemsBeforeUpdate = _filter(sortableContainer.children, addData(sortableContainer, 'items'))
1013
+ .filter(function (item) { return item !== store(sortableElement).placeholder; });
1014
+ if (options.dropTargetContainerClass) {
1015
+ sortableContainer.classList.add(options.dropTargetContainerClass);
1016
+ }
1017
+ sortableContainer.dispatchEvent(new CustomEvent('sortenter', {
1018
+ detail: {
1019
+ origin: {
1020
+ elementIndex: originElementIndex,
1021
+ index: originIndex,
1022
+ container: originContainer
1023
+ },
1024
+ destination: {
1025
+ container: sortableContainer,
1026
+ itemsBeforeUpdate: destinationItemsBeforeUpdate
1027
+ },
1028
+ item: dragging,
1029
+ originalTarget: target
1030
+ }
1031
+ }));
1032
+ addEventListener(sortableContainer, 'dragleave', function (e) {
1033
+ // TODO: rename outTarget to be more self-explanatory
1034
+ // e.fromElement for very old browsers, similar to relatedTarget
1035
+ var outTarget = e.relatedTarget || e.fromElement;
1036
+ if (!e.currentTarget.contains(outTarget)) {
1037
+ if (options.dropTargetContainerClass) {
1038
+ sortableContainer.classList.remove(options.dropTargetContainerClass);
1039
+ }
1040
+ sortableContainer.dispatchEvent(new CustomEvent('sortleave', {
1041
+ detail: {
1042
+ origin: {
1043
+ elementIndex: originElementIndex,
1044
+ index: originIndex,
1045
+ container: sortableContainer
1046
+ },
1047
+ item: dragging,
1048
+ originalTarget: target
1049
+ }
1050
+ }));
1051
+ }
1052
+ });
1053
+ }
1054
+ previousContainer = sortableContainer;
1055
+ });
1056
+ /*
1057
+ * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend
1058
+ * Fires each time dragEvent end, or ESC pressed
1059
+ * We are using it to clean up any draggable elements and placeholders
1060
+ */
1061
+ addEventListener(sortableElement, 'dragend', function (e) {
1062
+ if (!dragging) {
1063
+ return;
1064
+ }
1065
+ dragging.classList.remove(options.draggingClass);
1066
+ addAttribute(dragging, 'aria-grabbed', 'false');
1067
+ if (dragging.getAttribute('aria-copied') === 'true' && addData(dragging, 'dropped') !== 'true') {
1068
+ dragging.remove();
1069
+ }
1070
+ dragging.style.display = dragging.oldDisplay;
1071
+ delete dragging.oldDisplay;
1072
+ var visiblePlaceholder = Array.from(stores.values()).map(function (data) { return data.placeholder; })
1073
+ .filter(function (placeholder) { return placeholder instanceof HTMLElement; })
1074
+ .filter(isInDom)[0];
1075
+ if (visiblePlaceholder) {
1076
+ visiblePlaceholder.remove();
1077
+ }
1078
+ // dispatch sortstart event on each element in group
1079
+ sortableElement.dispatchEvent(new CustomEvent('sortstop', {
1080
+ detail: {
1081
+ origin: {
1082
+ elementIndex: originElementIndex,
1083
+ index: originIndex,
1084
+ container: originContainer
1085
+ },
1086
+ item: dragging
1087
+ }
1088
+ }));
1089
+ previousContainer = null;
1090
+ dragging = null;
1091
+ draggingHeight = null;
1092
+ draggingWidth = null;
1093
+ });
1094
+ /*
1095
+ * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop
1096
+ * Fires when valid drop target area is hit
1097
+ */
1098
+ addEventListener(sortableElement, 'drop', function (e) {
1099
+ if (!_listsConnected(sortableElement, dragging.parentElement)) {
1100
+ return;
1101
+ }
1102
+ e.preventDefault();
1103
+ e.stopPropagation();
1104
+ addData(dragging, 'dropped', 'true');
1105
+ // get the one placeholder that is currently visible
1106
+ var visiblePlaceholder = Array.from(stores.values()).map(function (data) {
1107
+ return data.placeholder;
1108
+ })
1109
+ // filter only HTMLElements
1110
+ .filter(function (placeholder) { return placeholder instanceof HTMLElement; })
1111
+ // filter only elements in DOM
1112
+ .filter(isInDom)[0];
1113
+ // attach element after placeholder
1114
+ insertAfter(visiblePlaceholder, dragging);
1115
+ // remove placeholder from dom
1116
+ visiblePlaceholder.remove();
1117
+ /*
1118
+ * Fires Custom Event - 'sortstop'
1119
+ */
1120
+ sortableElement.dispatchEvent(new CustomEvent('sortstop', {
1121
+ detail: {
1122
+ origin: {
1123
+ elementIndex: originElementIndex,
1124
+ index: originIndex,
1125
+ container: originContainer
1126
+ },
1127
+ item: dragging
1128
+ }
1129
+ }));
1130
+ var placeholder = store(sortableElement).placeholder;
1131
+ var originItems = _filter(originContainer.children, options.items)
1132
+ .filter(function (item) { return item !== placeholder; });
1133
+ var destinationContainer = this.isSortable === true ? this : this.parentElement;
1134
+ var destinationItems = _filter(destinationContainer.children, addData(destinationContainer, 'items'))
1135
+ .filter(function (item) { return item !== placeholder; });
1136
+ var destinationElementIndex = _index(dragging, Array.from(dragging.parentElement.children)
1137
+ .filter(function (item) { return item !== placeholder; }));
1138
+ var destinationIndex = _index(dragging, destinationItems);
1139
+ if (options.dropTargetContainerClass) {
1140
+ destinationContainer.classList.remove(options.dropTargetContainerClass);
1141
+ }
1142
+ /*
1143
+ * When a list item changed container lists or index within a list
1144
+ * Fires Custom Event - 'sortupdate'
1145
+ */
1146
+ if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) {
1147
+ sortableElement.dispatchEvent(new CustomEvent('sortupdate', {
1148
+ detail: {
1149
+ origin: {
1150
+ elementIndex: originElementIndex,
1151
+ index: originIndex,
1152
+ container: originContainer,
1153
+ itemsBeforeUpdate: originItemsBeforeUpdate,
1154
+ items: originItems
1155
+ },
1156
+ destination: {
1157
+ index: destinationIndex,
1158
+ elementIndex: destinationElementIndex,
1159
+ container: destinationContainer,
1160
+ itemsBeforeUpdate: destinationItemsBeforeUpdate,
1161
+ items: destinationItems
1162
+ },
1163
+ item: dragging
1164
+ }
1165
+ }));
1166
+ }
1167
+ });
1168
+ var debouncedDragOverEnter = _debounce(function (sortableElement, element, pageX, pageY) {
1169
+ if (!dragging) {
1170
+ return;
1171
+ }
1172
+ // set placeholder height if forcePlaceholderSize option is set
1173
+ if (options.forcePlaceholderSize) {
1174
+ store(sortableElement).placeholder.style.height = draggingHeight + 'px';
1175
+ store(sortableElement).placeholder.style.width = draggingWidth + 'px';
1176
+ }
1177
+ // if element the draggedItem is dragged onto is within the array of all elements in list
1178
+ // (not only items, but also disabled, etc.)
1179
+ if (Array.from(sortableElement.children).indexOf(element) > -1) {
1180
+ var thisHeight = _getElementHeight(element);
1181
+ var thisWidth = _getElementWidth(element);
1182
+ var placeholderIndex = _index(store(sortableElement).placeholder, element.parentElement.children);
1183
+ var thisIndex = _index(element, element.parentElement.children);
1184
+ // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering
1185
+ if (thisHeight > draggingHeight || thisWidth > draggingWidth) {
1186
+ // Dead zone?
1187
+ var deadZoneVertical = thisHeight - draggingHeight;
1188
+ var deadZoneHorizontal = thisWidth - draggingWidth;
1189
+ var offsetTop = _offset(element).top;
1190
+ var offsetLeft = _offset(element).left;
1191
+ if (placeholderIndex < thisIndex &&
1192
+ ((options.orientation === 'vertical' && pageY < offsetTop) ||
1193
+ (options.orientation === 'horizontal' && pageX < offsetLeft))) {
1194
+ return;
1195
+ }
1196
+ if (placeholderIndex > thisIndex &&
1197
+ ((options.orientation === 'vertical' && pageY > offsetTop + thisHeight - deadZoneVertical) ||
1198
+ (options.orientation === 'horizontal' && pageX > offsetLeft + thisWidth - deadZoneHorizontal))) {
1199
+ return;
1200
+ }
1201
+ }
1202
+ if (dragging.oldDisplay === undefined) {
1203
+ dragging.oldDisplay = dragging.style.display;
1204
+ }
1205
+ if (dragging.style.display !== 'none') {
1206
+ dragging.style.display = 'none';
1207
+ }
1208
+ // To avoid flicker, determine where to position the placeholder
1209
+ // based on where the mouse pointer is relative to the elements
1210
+ // vertical center.
1211
+ var placeAfter = false;
1212
+ try {
1213
+ var elementMiddleVertical = _offset(element).top + element.offsetHeight / 2;
1214
+ var elementMiddleHorizontal = _offset(element).left + element.offsetWidth / 2;
1215
+ placeAfter = (options.orientation === 'vertical' && (pageY >= elementMiddleVertical)) ||
1216
+ (options.orientation === 'horizontal' && (pageX >= elementMiddleHorizontal));
1217
+ }
1218
+ catch (e) {
1219
+ placeAfter = placeholderIndex < thisIndex;
1220
+ }
1221
+ if (placeAfter) {
1222
+ insertAfter(element, store(sortableElement).placeholder);
1223
+ }
1224
+ else {
1225
+ insertBefore(element, store(sortableElement).placeholder);
1226
+ }
1227
+ // get placeholders from all stores & remove all but current one
1228
+ Array.from(stores.values())
1229
+ // remove empty values
1230
+ .filter(function (data) { return data.placeholder !== undefined; })
1231
+ // foreach placeholder in array if outside of current sorableContainer -> remove from DOM
1232
+ .forEach(function (data) {
1233
+ if (data.placeholder !== store(sortableElement).placeholder) {
1234
+ data.placeholder.remove();
1235
+ }
1236
+ });
1237
+ }
1238
+ else {
1239
+ // get all placeholders from store
1240
+ var placeholders = Array.from(stores.values())
1241
+ .filter(function (data) { return data.placeholder !== undefined; })
1242
+ .map(function (data) {
1243
+ return data.placeholder;
1244
+ });
1245
+ // check if element is not in placeholders
1246
+ if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) {
1247
+ placeholders.forEach(function (element) { return element.remove(); });
1248
+ element.appendChild(store(sortableElement).placeholder);
1249
+ }
1250
+ }
1251
+ }, options.debounce);
1252
+ // Handle dragover and dragenter events on draggable items
1253
+ var onDragOverEnter = function (e) {
1254
+ var element = e.target;
1255
+ var sortableElement = element.isSortable === true ? element : findSortable(element, e);
1256
+ element = findDragElement(sortableElement, element);
1257
+ if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || addData(sortableElement, '_disabled') === 'true') {
1258
+ return;
1259
+ }
1260
+ var options = addData(sortableElement, 'opts');
1261
+ if (parseInt(options.maxItems) && _filter(sortableElement.children, addData(sortableElement, 'items')).length >= parseInt(options.maxItems) && dragging.parentElement !== sortableElement) {
1262
+ return;
1263
+ }
1264
+ e.preventDefault();
1265
+ e.stopPropagation();
1266
+ e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move';
1267
+ debouncedDragOverEnter(sortableElement, element, e.pageX, e.pageY);
1268
+ };
1269
+ addEventListener(listItems.concat(sortableElement), 'dragover', onDragOverEnter);
1270
+ addEventListener(listItems.concat(sortableElement), 'dragenter', onDragOverEnter);
1271
+ });
1272
+ return sortableElements;
1273
+ }
1274
+ sortable.destroy = function (sortableElement) {
1275
+ _destroySortable(sortableElement);
1276
+ };
1277
+ sortable.enable = function (sortableElement) {
1278
+ _enableSortable(sortableElement);
1279
+ };
1280
+ sortable.disable = function (sortableElement) {
1281
+ _disableSortable(sortableElement);
1282
+ };
1283
+ /* START.TESTS_ONLY */
1284
+ sortable.__testing = {
1285
+ // add internal methods here for testing purposes
1286
+ _data: addData,
1287
+ _removeItemEvents: _removeItemEvents,
1288
+ _removeItemData: _removeItemData,
1289
+ _removeSortableData: _removeSortableData,
1290
+ _removeContainerEvents: _removeContainerEvents
1291
+ };
1292
+
1293
+ return sortable;
1294
+
1295
+ }());