94 lines
2.9 KiB
TypeScript
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 = element.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 = element.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')
|
|
);
|
|
}
|