feat(turbo): add turbo:morph helper

This commit is contained in:
Paul Chavard 2022-05-09 18:48:52 +02:00 committed by Paul Chavard
parent 65bd996f2a
commit cf81e8ecd5
4 changed files with 61 additions and 3 deletions

View file

@ -39,5 +39,15 @@ module TurboStreamHelper
def enable(target)
dispatch('dom:mutation', { action: :enable, target: target })
end
def morph(target, content = nil, **rendering, &block)
template = render_template(target, content, allow_inferred_rendering: true, **rendering, &block)
dispatch('dom:mutation', { action: :morph, target: target, html: template })
end
def morph_all(targets, content = nil, **rendering, &block)
template = render_template(targets, content, allow_inferred_rendering: true, **rendering, &block)
dispatch('dom:mutation', { action: :morph, targets: targets, html: template })
end
end
end

View file

@ -1,5 +1,6 @@
import invariant from 'tiny-invariant';
import { z } from 'zod';
import morphdom from 'morphdom';
import { ApplicationController, Detail } from './application_controller';
@ -18,18 +19,27 @@ export class TurboEventController extends ApplicationController {
}
}
const MutationAction = z.enum(['show', 'hide', 'focus', 'enable', 'disable']);
const MutationAction = z.enum([
'show',
'hide',
'focus',
'enable',
'disable',
'morph'
]);
type MutationAction = z.infer<typeof MutationAction>;
const Mutation = z.union([
z.object({
action: MutationAction,
delay: z.number().optional(),
target: z.string()
target: z.string(),
html: z.string().optional()
}),
z.object({
action: MutationAction,
delay: z.number().optional(),
targets: z.string()
targets: z.string(),
html: z.string().optional()
})
]);
type Mutation = z.infer<typeof Mutation>;
@ -65,9 +75,41 @@ const Mutations: Record<MutationAction, (mutation: Mutation) => void> = {
for (const element of findElements<HTMLInputElement>(mutation)) {
element.disabled = false;
}
},
morph: (mutation) => {
invariant(mutation.html, 'morph action requires html');
for (const element of findElements<HTMLInputElement>(mutation)) {
morphdom(element, mutation.html, {
onBeforeElUpdated(fromEl, toEl) {
if (isTouchedInput(fromEl)) {
fromEl.removeAttribute('data-touched');
mergeInputValue(
fromEl as HTMLInputElement,
toEl as HTMLInputElement
);
}
if (fromEl.isEqualNode(toEl)) {
return false;
}
return true;
}
});
}
}
};
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')
);
}
function mutate(mutation: Mutation) {
const fn = Mutations[mutation.action];
invariant(fn, `Could not find mutation ${mutation.action}`);

View file

@ -34,6 +34,7 @@
"is-hotkey": "^0.2.0",
"maplibre-gl": "^1.15.2",
"match-sorter": "^6.2.0",
"morphdom": "^2.6.1",
"patch-package": "^6.4.7",
"react": "^18.0.0",
"react-coordinate-input": "^1.0.0",

View file

@ -9211,6 +9211,11 @@ moize@^6.0.0:
fast-equals "^2.0.1"
micro-memoize "^4.0.9"
morphdom@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/morphdom/-/morphdom-2.6.1.tgz#e868e24f989fa3183004b159aed643e628b4306e"
integrity sha512-Y8YRbAEP3eKykroIBWrjcfMw7mmwJfjhqdpSvoqinu8Y702nAwikpXcNFDiIkyvfCLxLM9Wu95RZqo4a9jFBaA==
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"