mirror of
https://github.com/immich-app/immich.git
synced 2025-12-20 09:15:35 +03:00
refactor(web): svelte actions (#9701)
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
export const autoGrowHeight = (textarea: HTMLTextAreaElement, height = 'auto') => {
|
||||
textarea.style.height = height;
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
import type { ActionReturn } from 'svelte/action';
|
||||
import { matchesShortcut } from './shortcut';
|
||||
|
||||
interface Attributes {
|
||||
/** @deprecated */
|
||||
'on:outclick'?: (e: CustomEvent) => void;
|
||||
/** @deprecated **/
|
||||
'on:escape'?: (e: CustomEvent) => void;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
onOutclick?: () => void;
|
||||
onEscape?: () => void;
|
||||
}
|
||||
|
||||
export function clickOutside(node: HTMLElement, options: Options = {}): ActionReturn<void, Attributes> {
|
||||
const { onOutclick, onEscape } = options;
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
const targetNode = event.target as Node | null;
|
||||
if (node.contains(targetNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onOutclick) {
|
||||
onOutclick();
|
||||
} else {
|
||||
node.dispatchEvent(new CustomEvent('outclick'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleKey = (event: KeyboardEvent) => {
|
||||
if (!matchesShortcut(event, { key: 'Escape' })) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onEscape) {
|
||||
event.stopPropagation();
|
||||
onEscape();
|
||||
} else {
|
||||
node.dispatchEvent(new CustomEvent('escape'));
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick, true);
|
||||
node.addEventListener('keydown', handleKey, false);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
node.removeEventListener('keydown', handleKey, false);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
interface Options {
|
||||
onFocusOut?: () => void;
|
||||
}
|
||||
|
||||
export function focusOutside(node: HTMLElement, options: Options = {}) {
|
||||
const { onFocusOut } = options;
|
||||
|
||||
const handleFocusOut = (event: FocusEvent) => {
|
||||
if (onFocusOut && event.relatedTarget instanceof Node && !node.contains(event.relatedTarget as Node)) {
|
||||
onFocusOut();
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener('focusout', handleFocusOut);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener('focusout', handleFocusOut);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { Action } from 'svelte/action';
|
||||
import { shortcuts } from './shortcut';
|
||||
|
||||
export const listNavigation: Action<HTMLElement, HTMLElement> = (node, container: HTMLElement) => {
|
||||
const moveFocus = (direction: 'up' | 'down') => {
|
||||
const children = Array.from(container?.children);
|
||||
if (children.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIndex = document.activeElement === null ? -1 : children.indexOf(document.activeElement);
|
||||
const directionFactor = (direction === 'up' ? -1 : 1) + (direction === 'up' && currentIndex === -1 ? 1 : 0);
|
||||
const newIndex = (currentIndex + directionFactor + children.length) % children.length;
|
||||
|
||||
const element = children.at(newIndex);
|
||||
if (element instanceof HTMLElement) {
|
||||
element.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const { destroy } = shortcuts(node, [
|
||||
{ shortcut: { key: 'ArrowUp' }, onShortcut: () => moveFocus('up'), ignoreInputFields: false },
|
||||
{ shortcut: { key: 'ArrowDown' }, onShortcut: () => moveFocus('down'), ignoreInputFields: false },
|
||||
]);
|
||||
|
||||
return {
|
||||
update(newContainer) {
|
||||
container = newContainer;
|
||||
},
|
||||
destroy,
|
||||
};
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
import type { ActionReturn } from 'svelte/action';
|
||||
|
||||
export type Shortcut = {
|
||||
key: string;
|
||||
alt?: boolean;
|
||||
ctrl?: boolean;
|
||||
shift?: boolean;
|
||||
meta?: boolean;
|
||||
};
|
||||
|
||||
export type ShortcutOptions<T = HTMLElement> = {
|
||||
shortcut: Shortcut;
|
||||
ignoreInputFields?: boolean;
|
||||
onShortcut: (event: KeyboardEvent & { currentTarget: T }) => unknown;
|
||||
preventDefault?: boolean;
|
||||
};
|
||||
|
||||
export const shouldIgnoreShortcut = (event: KeyboardEvent): boolean => {
|
||||
if (event.target === event.currentTarget) {
|
||||
return false;
|
||||
}
|
||||
const type = (event.target as HTMLInputElement).type;
|
||||
return ['textarea', 'text', 'date', 'datetime-local'].includes(type);
|
||||
};
|
||||
|
||||
export const matchesShortcut = (event: KeyboardEvent, shortcut: Shortcut) => {
|
||||
return (
|
||||
shortcut.key.toLowerCase() === event.key.toLowerCase() &&
|
||||
Boolean(shortcut.alt) === event.altKey &&
|
||||
Boolean(shortcut.ctrl) === event.ctrlKey &&
|
||||
Boolean(shortcut.shift) === event.shiftKey &&
|
||||
Boolean(shortcut.meta) === event.metaKey
|
||||
);
|
||||
};
|
||||
|
||||
export const shortcut = <T extends HTMLElement>(
|
||||
node: T,
|
||||
option: ShortcutOptions<T>,
|
||||
): ActionReturn<ShortcutOptions<T>> => {
|
||||
const { update: shortcutsUpdate, destroy } = shortcuts(node, [option]);
|
||||
|
||||
return {
|
||||
update(newOption) {
|
||||
shortcutsUpdate?.([newOption]);
|
||||
},
|
||||
destroy,
|
||||
};
|
||||
};
|
||||
|
||||
export const shortcuts = <T extends HTMLElement>(
|
||||
node: T,
|
||||
options: ShortcutOptions<T>[],
|
||||
): ActionReturn<ShortcutOptions<T>[]> => {
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
const ignoreShortcut = shouldIgnoreShortcut(event);
|
||||
|
||||
for (const { shortcut, onShortcut, ignoreInputFields = true, preventDefault = true } of options) {
|
||||
if (ignoreInputFields && ignoreShortcut) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (matchesShortcut(event, shortcut)) {
|
||||
if (preventDefault) {
|
||||
event.preventDefault();
|
||||
}
|
||||
onShortcut(event as KeyboardEvent & { currentTarget: T });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.addEventListener('keydown', onKeydown);
|
||||
|
||||
return {
|
||||
update(newOptions) {
|
||||
options = newOptions;
|
||||
},
|
||||
destroy() {
|
||||
node.removeEventListener('keydown', onKeydown);
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user