mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	refactor(web): remove events from clickOutside action (#9943)
This commit is contained in:
		
							parent
							
								
									5af67d159f
								
							
						
					
					
						commit
						d1135db8cf
					
				@ -1,19 +1,12 @@
 | 
				
			|||||||
import { matchesShortcut } from '$lib/actions/shortcut';
 | 
					import { matchesShortcut } from '$lib/actions/shortcut';
 | 
				
			||||||
import type { ActionReturn } from 'svelte/action';
 | 
					import type { ActionReturn } from 'svelte/action';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Attributes {
 | 
					 | 
				
			||||||
  /** @deprecated */
 | 
					 | 
				
			||||||
  'on:outclick'?: (e: CustomEvent) => void;
 | 
					 | 
				
			||||||
  /** @deprecated **/
 | 
					 | 
				
			||||||
  'on:escape'?: (e: CustomEvent) => void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Options {
 | 
					interface Options {
 | 
				
			||||||
  onOutclick?: () => void;
 | 
					  onOutclick?: () => void;
 | 
				
			||||||
  onEscape?: () => void;
 | 
					  onEscape?: () => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function clickOutside(node: HTMLElement, options: Options = {}): ActionReturn<void, Attributes> {
 | 
					export function clickOutside(node: HTMLElement, options: Options = {}): ActionReturn {
 | 
				
			||||||
  const { onOutclick, onEscape } = options;
 | 
					  const { onOutclick, onEscape } = options;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleClick = (event: MouseEvent) => {
 | 
					  const handleClick = (event: MouseEvent) => {
 | 
				
			||||||
@ -22,11 +15,7 @@ export function clickOutside(node: HTMLElement, options: Options = {}): ActionRe
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (onOutclick) {
 | 
					    onOutclick?.();
 | 
				
			||||||
      onOutclick();
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      node.dispatchEvent(new CustomEvent('outclick'));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleKey = (event: KeyboardEvent) => {
 | 
					  const handleKey = (event: KeyboardEvent) => {
 | 
				
			||||||
@ -37,8 +26,6 @@ export function clickOutside(node: HTMLElement, options: Options = {}): ActionRe
 | 
				
			|||||||
    if (onEscape) {
 | 
					    if (onEscape) {
 | 
				
			||||||
      event.stopPropagation();
 | 
					      event.stopPropagation();
 | 
				
			||||||
      onEscape();
 | 
					      onEscape();
 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      node.dispatchEvent(new CustomEvent('escape'));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -126,7 +126,7 @@
 | 
				
			|||||||
                />
 | 
					                />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                {#if selectedMenuUser === user}
 | 
					                {#if selectedMenuUser === user}
 | 
				
			||||||
                  <ContextMenu {...position} on:outclick={() => (selectedMenuUser = null)}>
 | 
					                  <ContextMenu {...position} onClose={() => (selectedMenuUser = null)}>
 | 
				
			||||||
                    {#if role === AlbumUserRole.Viewer}
 | 
					                    {#if role === AlbumUserRole.Viewer}
 | 
				
			||||||
                      <MenuOption on:click={() => handleSetReadonly(user, AlbumUserRole.Editor)} text="Allow edits" />
 | 
					                      <MenuOption on:click={() => handleSetReadonly(user, AlbumUserRole.Editor)} text="Allow edits" />
 | 
				
			||||||
                    {:else}
 | 
					                    {:else}
 | 
				
			||||||
 | 
				
			|||||||
@ -201,8 +201,7 @@
 | 
				
			|||||||
                  <button
 | 
					                  <button
 | 
				
			||||||
                    type="button"
 | 
					                    type="button"
 | 
				
			||||||
                    class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors"
 | 
					                    class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors"
 | 
				
			||||||
                    use:clickOutside
 | 
					                    use:clickOutside={{ onOutclick: () => (showDeleteReaction[index] = false) }}
 | 
				
			||||||
                    on:outclick={() => (showDeleteReaction[index] = false)}
 | 
					 | 
				
			||||||
                    on:click={() => handleDeleteReaction(reaction, index)}
 | 
					                    on:click={() => handleDeleteReaction(reaction, index)}
 | 
				
			||||||
                  >
 | 
					                  >
 | 
				
			||||||
                    Remove
 | 
					                    Remove
 | 
				
			||||||
@ -254,8 +253,7 @@
 | 
				
			|||||||
                    <button
 | 
					                    <button
 | 
				
			||||||
                      type="button"
 | 
					                      type="button"
 | 
				
			||||||
                      class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors"
 | 
					                      class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors"
 | 
				
			||||||
                      use:clickOutside
 | 
					                      use:clickOutside={{ onOutclick: () => (showDeleteReaction[index] = false) }}
 | 
				
			||||||
                      on:outclick={() => (showDeleteReaction[index] = false)}
 | 
					 | 
				
			||||||
                      on:click={() => handleDeleteReaction(reaction, index)}
 | 
					                      on:click={() => handleDeleteReaction(reaction, index)}
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                      Remove
 | 
					                      Remove
 | 
				
			||||||
 | 
				
			|||||||
@ -795,7 +795,6 @@
 | 
				
			|||||||
      <DeleteAssetDialog
 | 
					      <DeleteAssetDialog
 | 
				
			||||||
        size={1}
 | 
					        size={1}
 | 
				
			||||||
        on:cancel={() => (isShowDeleteConfirmation = false)}
 | 
					        on:cancel={() => (isShowDeleteConfirmation = false)}
 | 
				
			||||||
        on:escape={() => (isShowDeleteConfirmation = false)}
 | 
					 | 
				
			||||||
        on:confirm={() => deleteAsset()}
 | 
					        on:confirm={() => deleteAsset()}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -72,7 +72,7 @@
 | 
				
			|||||||
  $: renderedSelectedOption = renderOption(selectedOption);
 | 
					  $: renderedSelectedOption = renderOption(selectedOption);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}>
 | 
					<div use:clickOutside={{ onOutclick: handleClickOutside, onEscape: handleClickOutside }}>
 | 
				
			||||||
  <!-- BUTTON TITLE -->
 | 
					  <!-- BUTTON TITLE -->
 | 
				
			||||||
  <LinkButton on:click={() => (showMenu = true)} fullwidth {title}>
 | 
					  <LinkButton on:click={() => (showMenu = true)} fullwidth {title}>
 | 
				
			||||||
    <div class="flex place-items-center gap-2 text-sm">
 | 
					    <div class="flex place-items-center gap-2 text-sm">
 | 
				
			||||||
 | 
				
			|||||||
@ -87,7 +87,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
{#if showContextMenu}
 | 
					{#if showContextMenu}
 | 
				
			||||||
  <Portal target="body">
 | 
					  <Portal target="body">
 | 
				
			||||||
    <ContextMenu {...contextMenuPosition} on:outclick={() => onMenuExit()}>
 | 
					    <ContextMenu {...contextMenuPosition} onClose={() => onMenuExit()}>
 | 
				
			||||||
      <MenuOption on:click={() => onMenuClick('hide-person')} icon={mdiEyeOffOutline} text="Hide person" />
 | 
					      <MenuOption on:click={() => onMenuClick('hide-person')} icon={mdiEyeOffOutline} text="Hide person" />
 | 
				
			||||||
      <MenuOption on:click={() => onMenuClick('change-name')} icon={mdiAccountEditOutline} text="Change name" />
 | 
					      <MenuOption on:click={() => onMenuClick('change-name')} icon={mdiAccountEditOutline} text="Change name" />
 | 
				
			||||||
      <MenuOption
 | 
					      <MenuOption
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@
 | 
				
			|||||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
					  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
				
			||||||
  import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
 | 
					  import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
 | 
				
			||||||
  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
					  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
				
			||||||
  import { createEventDispatcher } from 'svelte';
 | 
					 | 
				
			||||||
  import { featureFlags } from '$lib/stores/server-config.store';
 | 
					  import { featureFlags } from '$lib/stores/server-config.store';
 | 
				
			||||||
  import { mdiTimerSand, mdiDeleteOutline } from '@mdi/js';
 | 
					  import { mdiTimerSand, mdiDeleteOutline } from '@mdi/js';
 | 
				
			||||||
  import { type OnDelete, deleteAssets } from '$lib/utils/actions';
 | 
					  import { type OnDelete, deleteAssets } from '$lib/utils/actions';
 | 
				
			||||||
@ -14,10 +13,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const { clearSelect, getOwnedAssets } = getAssetControlContext();
 | 
					  const { clearSelect, getOwnedAssets } = getAssetControlContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const dispatch = createEventDispatcher<{
 | 
					 | 
				
			||||||
    escape: void;
 | 
					 | 
				
			||||||
  }>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let isShowConfirmation = false;
 | 
					  let isShowConfirmation = false;
 | 
				
			||||||
  let loading = false;
 | 
					  let loading = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -40,11 +35,6 @@
 | 
				
			|||||||
    isShowConfirmation = false;
 | 
					    isShowConfirmation = false;
 | 
				
			||||||
    loading = false;
 | 
					    loading = false;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					 | 
				
			||||||
  const escape = () => {
 | 
					 | 
				
			||||||
    dispatch('escape');
 | 
					 | 
				
			||||||
    isShowConfirmation = false;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if menuItem}
 | 
					{#if menuItem}
 | 
				
			||||||
@ -60,6 +50,5 @@
 | 
				
			|||||||
    size={getOwnedAssets().size}
 | 
					    size={getOwnedAssets().size}
 | 
				
			||||||
    on:confirm={handleDelete}
 | 
					    on:confirm={handleDelete}
 | 
				
			||||||
    on:cancel={() => (isShowConfirmation = false)}
 | 
					    on:cancel={() => (isShowConfirmation = false)}
 | 
				
			||||||
    on:escape={escape}
 | 
					 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@
 | 
				
			|||||||
  setContext(() => (showContextMenu = false));
 | 
					  setContext(() => (showContextMenu = false));
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div use:clickOutside on:outclick={() => (showContextMenu = false)}>
 | 
					<div use:clickOutside={{ onOutclick: () => (showContextMenu = false) }}>
 | 
				
			||||||
  <CircleIconButton {title} {icon} on:click={handleShowMenu} />
 | 
					  <CircleIconButton {title} {icon} on:click={handleShowMenu} />
 | 
				
			||||||
  {#if showContextMenu}
 | 
					  {#if showContextMenu}
 | 
				
			||||||
    <ContextMenu {...contextMenuPosition}>
 | 
					    <ContextMenu {...contextMenuPosition}>
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@
 | 
				
			|||||||
  export let y = 0;
 | 
					  export let y = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let menuElement: HTMLDivElement | undefined = undefined;
 | 
					  export let menuElement: HTMLDivElement | undefined = undefined;
 | 
				
			||||||
 | 
					  export let onClose: (() => void) | undefined = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let left: number;
 | 
					  let left: number;
 | 
				
			||||||
  let top: number;
 | 
					  let top: number;
 | 
				
			||||||
@ -36,9 +37,7 @@
 | 
				
			|||||||
  style:top="{top}px"
 | 
					  style:top="{top}px"
 | 
				
			||||||
  style:left="{left}px"
 | 
					  style:left="{left}px"
 | 
				
			||||||
  role="menu"
 | 
					  role="menu"
 | 
				
			||||||
  use:clickOutside
 | 
					  use:clickOutside={{ onOutclick: onClose, onEscape: onClose }}
 | 
				
			||||||
  on:outclick
 | 
					 | 
				
			||||||
  on:escape
 | 
					 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
  <div class="flex flex-col rounded-lg">
 | 
					  <div class="flex flex-col rounded-lg">
 | 
				
			||||||
    <slot />
 | 
					    <slot />
 | 
				
			||||||
 | 
				
			|||||||
@ -49,14 +49,7 @@
 | 
				
			|||||||
      on:contextmenu|preventDefault={reopenContextMenu}
 | 
					      on:contextmenu|preventDefault={reopenContextMenu}
 | 
				
			||||||
      role="presentation"
 | 
					      role="presentation"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <ContextMenu
 | 
					      <ContextMenu {x} {y} {direction} onClose={closeContextMenu} bind:menuElement={contextMenuElement}>
 | 
				
			||||||
        {x}
 | 
					 | 
				
			||||||
        {y}
 | 
					 | 
				
			||||||
        {direction}
 | 
					 | 
				
			||||||
        on:outclick={closeContextMenu}
 | 
					 | 
				
			||||||
        on:escape={closeContextMenu}
 | 
					 | 
				
			||||||
        bind:menuElement={contextMenuElement}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <slot />
 | 
					        <slot />
 | 
				
			||||||
      </ContextMenu>
 | 
					      </ContextMenu>
 | 
				
			||||||
    </section>
 | 
					    </section>
 | 
				
			||||||
 | 
				
			|||||||
@ -114,9 +114,10 @@
 | 
				
			|||||||
        {/if}
 | 
					        {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          use:clickOutside
 | 
					          use:clickOutside={{
 | 
				
			||||||
          on:outclick={() => (shouldShowAccountInfoPanel = false)}
 | 
					            onOutclick: () => (shouldShowAccountInfoPanel = false),
 | 
				
			||||||
          on:escape={() => (shouldShowAccountInfoPanel = false)}
 | 
					            onEscape: () => (shouldShowAccountInfoPanel = false),
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <button
 | 
					          <button
 | 
				
			||||||
            type="button"
 | 
					            type="button"
 | 
				
			||||||
 | 
				
			|||||||
@ -467,7 +467,7 @@
 | 
				
			|||||||
              <CircleIconButton title="Download" on:click={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
 | 
					              <CircleIconButton title="Download" on:click={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              {#if isOwned}
 | 
					              {#if isOwned}
 | 
				
			||||||
                <div use:clickOutside on:outclick={() => (viewMode = ViewMode.VIEW)}>
 | 
					                <div use:clickOutside={{ onOutclick: () => (viewMode = ViewMode.VIEW) }}>
 | 
				
			||||||
                  <CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical} />
 | 
					                  <CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical} />
 | 
				
			||||||
                  {#if viewMode === ViewMode.ALBUM_OPTIONS}
 | 
					                  {#if viewMode === ViewMode.ALBUM_OPTIONS}
 | 
				
			||||||
                    <ContextMenu {...contextMenuPosition}>
 | 
					                    <ContextMenu {...contextMenuPosition}>
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,6 @@
 | 
				
			|||||||
  import { preferences, user } from '$lib/stores/user.store';
 | 
					  import { preferences, user } from '$lib/stores/user.store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let { isViewing: showAssetViewer } = assetViewingStore;
 | 
					  let { isViewing: showAssetViewer } = assetViewingStore;
 | 
				
			||||||
  let handleEscapeKey = false;
 | 
					 | 
				
			||||||
  const assetStore = new AssetStore({ isArchived: false, withStacked: true, withPartners: true });
 | 
					  const assetStore = new AssetStore({ isArchived: false, withStacked: true, withPartners: true });
 | 
				
			||||||
  const assetInteractionStore = createAssetInteractionStore();
 | 
					  const assetInteractionStore = createAssetInteractionStore();
 | 
				
			||||||
  const { isMultiSelectState, selectedAssets } = assetInteractionStore;
 | 
					  const { isMultiSelectState, selectedAssets } = assetInteractionStore;
 | 
				
			||||||
@ -43,10 +42,6 @@
 | 
				
			|||||||
    if ($showAssetViewer) {
 | 
					    if ($showAssetViewer) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (handleEscapeKey) {
 | 
					 | 
				
			||||||
      handleEscapeKey = false;
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if ($isMultiSelectState) {
 | 
					    if ($isMultiSelectState) {
 | 
				
			||||||
      assetInteractionStore.clearMultiselect();
 | 
					      assetInteractionStore.clearMultiselect();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
@ -79,11 +74,7 @@
 | 
				
			|||||||
      <ChangeDate menuItem />
 | 
					      <ChangeDate menuItem />
 | 
				
			||||||
      <ChangeLocation menuItem />
 | 
					      <ChangeLocation menuItem />
 | 
				
			||||||
      <ArchiveAction menuItem onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
 | 
					      <ArchiveAction menuItem onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
 | 
				
			||||||
      <DeleteAssets
 | 
					      <DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
 | 
				
			||||||
        menuItem
 | 
					 | 
				
			||||||
        on:escape={() => (handleEscapeKey = true)}
 | 
					 | 
				
			||||||
        onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <hr />
 | 
					      <hr />
 | 
				
			||||||
      <AssetJobActions />
 | 
					      <AssetJobActions />
 | 
				
			||||||
    </AssetSelectContextMenu>
 | 
					    </AssetSelectContextMenu>
 | 
				
			||||||
 | 
				
			|||||||
@ -397,7 +397,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                  {#if showContextMenu}
 | 
					                  {#if showContextMenu}
 | 
				
			||||||
                    <Portal target="body">
 | 
					                    <Portal target="body">
 | 
				
			||||||
                      <ContextMenu {...contextMenuPosition} on:outclick={() => onMenuExit()}>
 | 
					                      <ContextMenu {...contextMenuPosition} onClose={() => onMenuExit()}>
 | 
				
			||||||
                        <MenuOption on:click={() => onRenameClicked()} text={`Rename`} />
 | 
					                        <MenuOption on:click={() => onRenameClicked()} text={`Rename`} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        {#if selectedLibrary}
 | 
					                        {#if selectedLibrary}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user