Packages
phoenix_kit
1.7.13
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
priv/static/assets/phoenix_kit_sortable.js
/**
* PhoenixKit Sortable Grid
*
* Auto-loads SortableJS from CDN and provides drag-and-drop functionality
* for the sortable_grid component.
*
* SETUP: Add the hook to your app.js:
*
* // In assets/js/app.js, add to your Hooks object:
* import "../../../deps/phoenix_kit/priv/static/assets/phoenix_kit_sortable.js"
*
* let Hooks = {
* // ... your other hooks ...
* SortableGrid: window.SortableGridHook
* }
*
* let liveSocket = new LiveSocket("/live", Socket, {
* hooks: Hooks,
* // ... other options
* })
*
* That's it! The hook auto-loads SortableJS from CDN when needed.
*/
(function() {
"use strict";
// Prevent multiple initializations
if (window.PhoenixKitSortable) return;
window.PhoenixKitSortable = true;
var SORTABLE_CDN = "https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js";
var sortableLoading = false;
var sortableCallbacks = [];
var stylesInjected = false;
/**
* Inject CSS styles for sortable classes
*/
function injectStyles() {
if (stylesInjected) return;
stylesInjected = true;
var style = document.createElement("style");
style.textContent = [
".sortable-ghost { opacity: 0.5; }",
".sortable-chosen { outline: 2px solid oklch(var(--p)); outline-offset: 2px; }",
".sortable-drag { box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05); }"
].join("\n");
document.head.appendChild(style);
}
/**
* Load SortableJS from CDN if not already loaded
*/
function loadSortableJS(callback) {
if (window.Sortable) {
callback();
return;
}
sortableCallbacks.push(callback);
if (sortableLoading) return;
sortableLoading = true;
var script = document.createElement("script");
script.src = SORTABLE_CDN;
script.onload = function() {
sortableCallbacks.forEach(function(cb) { cb(); });
sortableCallbacks = [];
};
script.onerror = function() {
console.error("[PhoenixKitSortable] Failed to load SortableJS from CDN");
};
document.head.appendChild(script);
}
/**
* LiveView Hook for sortable grids
*/
var SortableGridHook = {
mounted: function() {
var self = this;
loadSortableJS(function() {
// Small delay to let LiveView finish any initial DOM morphing
setTimeout(function() {
self.initSortable();
}, 100);
});
},
updated: function() {
// Re-init if items changed
if (this.sortable) {
var currentItems = this.el.querySelectorAll(".sortable-item[data-id]");
if (currentItems.length !== this._itemCount) {
this.sortable.destroy();
this.initSortable();
}
}
},
destroyed: function() {
if (this.sortable) {
this.sortable.destroy();
this.sortable = null;
}
},
initSortable: function() {
var self = this;
var container = this.el;
var eventName = container.dataset.sortableEvent || "reorder_items";
// Inject CSS styles for sortable classes
injectStyles();
this._itemCount = container.querySelectorAll(".sortable-item[data-id]").length;
this.sortable = window.Sortable.create(container, {
animation: 150,
draggable: ".sortable-item",
filter: ".sortable-ignore",
forceFallback: true, // Use JS-based drag instead of native HTML5 drag (avoids image drag conflicts)
fallbackOnBody: true, // Fixes offset in modals with CSS transforms
ghostClass: "sortable-ghost",
chosenClass: "sortable-chosen",
dragClass: "sortable-drag",
onEnd: function(evt) {
// Get new order of item IDs
var items = container.querySelectorAll(".sortable-item[data-id]");
var orderedIds = Array.from(items).map(function(el) {
return el.dataset.id;
});
// Push event to LiveView
self.pushEvent(eventName, { ordered_ids: orderedIds });
}
});
}
};
// Export for window (hook registration)
window.SortableGridHook = SortableGridHook;
})();