spina-admin-journal 0.1.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 (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
+ }());