alpine-turbo-drive-adapter
Version:
Bridge library to enable full support for Alpine components in pages using Turbolinks/Turbo Drive
165 lines (159 loc) • 6.5 kB
JavaScript
function isValidVersion(required, current) {
var requiredArray = required.split('.');
var currentArray = current.split('.');
for (var i = 0; i < requiredArray.length; i++) {
if (currentArray[i] && currentArray[i] > requiredArray[i]) {
return true;
}
}
return currentArray[requiredArray.length - 1] === requiredArray[requiredArray.length - 1];
}
function dispatch(el, name) {
var detail = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
el.dispatchEvent(new CustomEvent(name, {
detail,
bubbles: true,
composed: true,
cancelable: true
}));
}
class Bridge {
init() {
if (!window.Alpine.version || !isValidVersion('3.0.0', window.Alpine.version)) {
throw new Error('Invalid Alpine version. Please use Alpine 3.0.0 or above');
}
// Tag all cloaked elements on first page load.
window.Alpine.mutateDom(() => {
document.body.querySelectorAll('[x-cloak]').forEach(el => {
var _el$getAttribute;
el.setAttribute('data-alpine-was-cloaked', (_el$getAttribute = el.getAttribute('x-cloak')) !== null && _el$getAttribute !== void 0 ? _el$getAttribute : '');
});
});
this.configureEventHandlers();
this.monkeyPatchDomSetAttributeToAllowAtSymbols();
}
configureEventHandlers() {
var renderCallback = event => {
if (event.detail.renderMethod === 'morph') return;
if (document.documentElement.hasAttribute('data-turbo-preview')) {
return;
}
dispatch(document, 'alpine:init');
dispatch(document, 'alpine:initializing');
window.Alpine.flushAndStopDeferringMutations();
dispatch(document, 'alpine:initialised');
window.Alpine.mutateDom(() => {
document.querySelectorAll('[data-alpine-ignored]').forEach(el => {
el.removeAttribute('x-ignore');
el.removeAttribute('data-alpine-ignored');
});
});
};
var beforeRenderCallback = event => {
if (event.detail.renderMethod === 'morph') return;
window.Alpine.mutateDom(() => {
if (document.documentElement.hasAttribute('data-turbo-preview')) {
return;
}
event.detail.newBody.querySelectorAll('[data-alpine-generated-me],[x-cloak]').forEach(el => {
if (el.hasAttribute('x-cloak')) {
var _el$getAttribute2;
el.setAttribute('data-alpine-was-cloaked', (_el$getAttribute2 = el.getAttribute('x-cloak')) !== null && _el$getAttribute2 !== void 0 ? _el$getAttribute2 : '');
}
if (el.hasAttribute('data-alpine-generated-me')) {
el.removeAttribute('data-alpine-generated-me');
el.remove();
}
});
});
window.Alpine.deferMutations();
};
var beforeCacheCallback = event => {
window.Alpine.mutateDom(() => {
document.body.querySelectorAll('[x-for],[x-if],[x-teleport],[data-alpine-was-cloaked]').forEach(el => {
if (el.hasAttribute('data-alpine-was-cloaked')) {
var _el$getAttribute3;
el.setAttribute('x-cloak', (_el$getAttribute3 = el.getAttribute('data-alpine-was-cloaked')) !== null && _el$getAttribute3 !== void 0 ? _el$getAttribute3 : '');
el.removeAttribute('data-alpine-was-cloaked');
}
if (el.hasAttribute('x-for') && el._x_lookup) {
Object.values(el._x_lookup).forEach(el => el.setAttribute('data-alpine-generated-me', true));
}
if (el.hasAttribute('x-if') && el._x_currentIfEl) {
el._x_currentIfEl.setAttribute('data-alpine-generated-me', true);
}
if (el.hasAttribute('x-teleport') && el._x_teleport) {
el._x_teleport.setAttribute('data-alpine-generated-me', true);
}
});
});
document.querySelectorAll('[data-turbo-permanent]').forEach(el => {
window.Alpine.mutateDom(() => {
if (!el.hasAttribute('x-ignore')) {
el.setAttribute('x-ignore', true);
el.setAttribute('data-alpine-ignored', true);
}
el.querySelectorAll('[data-alpine-generated-me]').forEach(sub => {
sub.removeAttribute('data-alpine-generated-me');
});
});
});
};
// This tricks Alpine into reinitializing a already-initialized
// tree, allowing us to avoid destroying the tree first.
var beforeMorphElementCallback = _ref => {
var {
target,
detail: {
newElement
}
} = _ref;
if (!newElement && target._x_dataStack) {
return window.Alpine.destroyTree(target);
}
delete target._x_marker;
};
var morphElementCallback = _ref2 => {
var {
target,
detail: {
newElement
}
} = _ref2;
newElement && target._x_dataStack && window.Alpine.initTree(target);
};
document.addEventListener('turbo:render', renderCallback);
document.addEventListener('turbo:before-render', beforeRenderCallback);
document.addEventListener('turbo:before-cache', beforeCacheCallback);
document.addEventListener('turbo:before-morph-element', beforeMorphElementCallback);
document.addEventListener('turbo:morph-element', morphElementCallback);
}
// Taken from https://github.com/alpinejs/alpine/blob/e363181057b3e71e5c03d2b88db576d5094be1c8/packages/morph/src/morph.js#L493
// Turbo/Idiomorph uses Element.setAttribute to morph attributes, which doesn't allow "@" symbols
monkeyPatchDomSetAttributeToAllowAtSymbols() {
var original = Element.prototype.setAttribute;
var hostDiv = document.createElement('div');
Element.prototype.setAttribute = function newSetAttribute(name, value) {
if (!name.includes('@')) {
return original.call(this, name, value);
}
hostDiv.innerHTML = "<span ".concat(name, "=\"").concat(value, "\"></span>");
var attr = hostDiv.firstElementChild.getAttributeNode(name);
hostDiv.firstElementChild.removeAttributeNode(attr);
this.setAttributeNode(attr);
};
}
}
if (window.Alpine) {
console.error('Alpine-turbo-drive-adapter must be included before AlpineJs');
}
// Polyfill for legacy browsers
if (!Object.getOwnPropertyDescriptor(NodeList.prototype, 'forEach')) {
Object.defineProperty(NodeList.prototype, 'forEach', Object.getOwnPropertyDescriptor(Array.prototype, 'forEach'));
}
document.addEventListener('alpine:init', () => {
var bridge = new Bridge();
bridge.init();
}, {
once: true
});