demarches-normaliennes/app/javascript/shared/turbo-actions.ts

94 lines
2.9 KiB
TypeScript

import { StreamActions, TurboStreamAction } from '@hotwired/turbo';
import morphdom from 'morphdom';
const hide: TurboStreamAction = function () {
this.targetElements.forEach((element: Element) => {
const delay = this.getAttribute('delay');
const hide = () => element.classList.add('hidden');
if (delay) {
setTimeout(hide, parseInt(delay, 10));
} else {
hide();
}
});
};
const show: TurboStreamAction = function () {
this.targetElements.forEach((element: Element) => {
const delay = this.getAttribute('delay');
const show = () => element.classList.remove('hidden');
if (delay) {
setTimeout(show, parseInt(delay, 10));
} else {
show();
}
});
};
const focus: TurboStreamAction = function () {
this.targetElements.forEach((element: HTMLInputElement) => element.focus());
};
const disable: TurboStreamAction = function () {
this.targetElements.forEach((element: HTMLInputElement) => {
element.disabled = true;
});
};
const enable: TurboStreamAction = function () {
this.targetElements.forEach((element: HTMLInputElement) => {
element.disabled = false;
});
};
const morph: TurboStreamAction = function () {
this.targetElements.forEach((element: Element) => {
let content: Element | DocumentFragment = this.templateContent;
// content.children ignores text node, the empty text nodes in particular
// so if templateContent contains an empty text node,
// we only keep the first element and happily morph it
if (content.children.length == 1) {
content = content.children[0];
}
// morphom morphes if content is an element
// swaps if content if a documentFragment
morphdom(element, content, {
onBeforeElUpdated: function (fromEl, toEl) {
if (isTouchedInput(fromEl)) {
fromEl.removeAttribute('data-touched');
mergeInputValue(fromEl as HTMLInputElement, toEl as HTMLInputElement);
}
if (fromEl.isEqualNode(toEl)) {
return false;
}
return true;
}
});
});
};
const dispatch: TurboStreamAction = function () {
const type = this.getAttribute('event-type') ?? '';
const detail = this.getAttribute('event-detail');
const event = new CustomEvent(type, {
detail: JSON.parse(detail ?? '{}'),
bubbles: true
});
document.documentElement.dispatchEvent(event);
};
StreamActions['hide'] = hide;
StreamActions['show'] = show;
StreamActions['focus'] = focus;
StreamActions['disable'] = disable;
StreamActions['enable'] = enable;
StreamActions['morph'] = morph;
StreamActions['dispatch'] = dispatch;
function mergeInputValue(fromEl: HTMLInputElement, toEl: HTMLInputElement) {
toEl.value = fromEl.value;
toEl.checked = fromEl.checked;
}
function isTouchedInput(element: HTMLElement): boolean {
return (
['INPUT', 'TEXTAREA', 'SELECT'].includes(element.tagName) &&
!!element.getAttribute('data-touched')
);
}