CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vueuse--integrations

Vue.js composition API wrappers for popular utility libraries enabling seamless integration of third-party tools

Pending
Overview
Eval results
Files

ui-interactions.mddocs/

UI Interactions

User interface enhancements including drag-and-drop sorting and focus management for improved accessibility and user experience.

Capabilities

useSortable

Drag-and-drop sorting functionality using SortableJS with reactive list updates.

/**
 * Drag-and-drop sorting with reactive list updates
 * @param selector - CSS selector for sortable container
 * @param list - Reactive array to keep in sync with DOM order
 * @param options - SortableJS and VueUse configuration options
 * @returns Sortable control interface
 */
function useSortable<T>(
  selector: string,
  list: MaybeRef<T[]>,
  options?: UseSortableOptions
): UseSortableReturn;

/**
 * Drag-and-drop sorting with element reference
 * @param el - Element or element getter function
 * @param list - Reactive array to keep in sync with DOM order
 * @param options - SortableJS and VueUse configuration options
 * @returns Sortable control interface
 */
function useSortable<T>(
  el: MaybeRefOrGetter<MaybeElement>,
  list: MaybeRef<T[]>,
  options?: UseSortableOptions
): UseSortableReturn;

interface UseSortableReturn {
  /** Start the sortable functionality */
  start: () => void;
  /** Stop the sortable functionality */
  stop: () => void;
  /** Get or set SortableJS options */
  option: (<K extends keyof Sortable.Options>(name: K, value: Sortable.Options[K]) => void) 
    & (<K extends keyof Sortable.Options>(name: K) => Sortable.Options[K]);
}

type UseSortableOptions = Sortable.Options & ConfigurableDocument;

// SortableJS Options (key ones)
namespace Sortable {
  interface Options {
    group?: string | GroupOptions;
    sort?: boolean;
    disabled?: boolean;
    animation?: number;
    handle?: string;
    filter?: string;
    draggable?: string;
    ghostClass?: string;
    chosenClass?: string;
    dragClass?: string;
    direction?: 'vertical' | 'horizontal';
    touchStartThreshold?: number;
    emptyInsertThreshold?: number;
    onStart?: (evt: SortableEvent) => void;
    onEnd?: (evt: SortableEvent) => void;
    onAdd?: (evt: SortableEvent) => void;
    onUpdate?: (evt: SortableEvent) => void;
    onSort?: (evt: SortableEvent) => void;
    onRemove?: (evt: SortableEvent) => void;
    onMove?: (evt: MoveEvent) => boolean | -1 | 1 | void;
    onClone?: (evt: SortableEvent) => void;
    onChange?: (evt: SortableEvent) => void;
  }

  interface SortableEvent {
    oldIndex?: number;
    newIndex?: number;
    item: HTMLElement;
    from: HTMLElement;
    to: HTMLElement;
    clone: HTMLElement;
  }
}

Usage Examples:

import { useSortable } from "@vueuse/integrations/useSortable";
import { ref } from 'vue';

// Basic sortable list
const items = ref(['Item 1', 'Item 2', 'Item 3', 'Item 4']);

const { start, stop } = useSortable('.sortable-list', items, {
  animation: 150,
  ghostClass: 'ghost',
  onEnd: (evt) => {
    console.log('Item moved from', evt.oldIndex, 'to', evt.newIndex);
  }
});

// With element reference
const sortableEl = ref<HTMLElement>();
const { start, stop } = useSortable(sortableEl, items);

// Advanced configuration
const todoItems = ref([
  { id: 1, text: 'Learn Vue.js', completed: false },
  { id: 2, text: 'Build an app', completed: false },
  { id: 3, text: 'Deploy to production', completed: true }
]);

const { start, stop, option } = useSortable('.todo-list', todoItems, {
  handle: '.drag-handle',
  filter: '.no-drag',
  animation: 200,
  ghostClass: 'sortable-ghost',
  chosenClass: 'sortable-chosen',
  dragClass: 'sortable-drag',
  onStart: (evt) => {
    console.log('Drag started');
  },
  onEnd: (evt) => {
    console.log('Drag ended');
    // List is automatically updated
  }
});

// Dynamic options
option('animation', 300);
const currentAnimation = option('animation');

// Multiple lists with shared group
const list1 = ref(['A', 'B', 'C']);
const list2 = ref(['X', 'Y', 'Z']);

useSortable('.list-1', list1, {
  group: 'shared',
  animation: 150
});

useSortable('.list-2', list2, {
  group: 'shared',
  animation: 150
});

Utility Functions

Helper functions for DOM manipulation in sortable contexts.

/**
 * Insert a DOM node at a specific index within a parent element
 * @param parentElement - Parent container element
 * @param element - Element to insert
 * @param index - Target index position
 */
function insertNodeAt(parentElement: Element, element: Element, index: number): void;

/**
 * Remove a DOM node from its parent
 * @param node - Node to remove
 */
function removeNode(node: Node): void;

/**
 * Move an array element from one index to another
 * @param list - Reactive array to modify
 * @param from - Source index
 * @param to - Target index
 * @param e - Optional SortableJS event for additional context
 */
function moveArrayElement<T>(
  list: MaybeRef<T[]>,
  from: number,
  to: number,
  e?: Sortable.SortableEvent | null
): void;

UseSortable Component

Declarative sortable component for template-based usage.

/**
 * Declarative sortable component
 */
const UseSortable = defineComponent({
  name: 'UseSortable',
  props: {
    /** Reactive array model */
    modelValue: {
      type: Array as PropType<any[]>,
      required: true
    },
    /** HTML tag to render */
    tag: {
      type: String,
      default: 'div'
    },
    /** SortableJS options */
    options: {
      type: Object as PropType<UseSortableOptions>,
      required: true
    }
  },
  emits: ['update:modelValue'],
  slots: {
    default: (props: {
      item: any;
      index: number;
    }) => any;
  }
});

Component Usage:

<template>
  <UseSortable
    v-model="items"
    tag="ul"
    :options="sortableOptions"
    class="sortable-list"
  >
    <template #default="{ item, index }">
      <li :key="item.id" class="sortable-item">
        <span class="drag-handle">⋮⋮</span>
        {{ item.text }}
        <button @click="removeItem(index)">Remove</button>
      </li>
    </template>
  </UseSortable>
</template>

<script setup>
import { UseSortable } from "@vueuse/integrations/useSortable";
import { ref } from 'vue';

const items = ref([
  { id: 1, text: 'First item' },
  { id: 2, text: 'Second item' },
  { id: 3, text: 'Third item' }
]);

const sortableOptions = {
  handle: '.drag-handle',
  animation: 150,
  ghostClass: 'ghost-item'
};

const removeItem = (index) => {
  items.value.splice(index, 1);
};
</script>

<style>
.sortable-list {
  list-style: none;
  padding: 0;
}

.sortable-item {
  padding: 10px;
  border: 1px solid #ddd;
  margin-bottom: 5px;
  cursor: move;
}

.ghost-item {
  opacity: 0.5;
}

.drag-handle {
  color: #999;
  margin-right: 10px;
  cursor: grab;
}
</style>

useFocusTrap

Focus management and accessibility enhancement using focus-trap.

/**
 * Focus management and accessibility enhancement
 * @param target - Target element(s) to trap focus within
 * @param options - Focus trap configuration options
 * @returns Focus trap control interface
 */
function useFocusTrap(
  target: MaybeRefOrGetter<Arrayable<MaybeRefOrGetter<string> | MaybeComputedElementRef>>,
  options?: UseFocusTrapOptions
): UseFocusTrapReturn;

interface UseFocusTrapReturn {
  /** Whether focus trap currently has focus */
  hasFocus: ShallowRef<boolean>;
  /** Whether focus trap is paused */
  isPaused: ShallowRef<boolean>;
  /** Activate the focus trap */
  activate: (opts?: ActivateOptions) => void;
  /** Deactivate the focus trap */
  deactivate: (opts?: DeactivateOptions) => void;
  /** Pause the focus trap */
  pause: Fn;
  /** Unpause the focus trap */
  unpause: Fn;
}

interface UseFocusTrapOptions extends Options {
  /** Activate focus trap immediately */
  immediate?: boolean;
}

// focus-trap options
interface Options {
  onActivate?: (focusTrapInstance: FocusTrap) => void;
  onDeactivate?: (focusTrapInstance: FocusTrap) => void;
  onPause?: (focusTrapInstance: FocusTrap) => void;
  onUnpause?: (focusTrapInstance: FocusTrap) => void;
  onPostActivate?: (focusTrapInstance: FocusTrap) => void;
  onPostDeactivate?: (focusTrapInstance: FocusTrap) => void;
  checkCanFocusTrap?: (focusTrapContainers: HTMLElement[]) => Promise<void>;
  checkCanReturnFocus?: (triggerElement: HTMLElement) => Promise<void>;
  initialFocus?: string | HTMLElement | (() => HTMLElement | string) | false;
  fallbackFocus?: string | HTMLElement | (() => HTMLElement | string);
  escapeDeactivates?: boolean | ((e: KeyboardEvent) => boolean);
  clickOutsideDeactivates?: boolean | ((e: MouseEvent | TouchEvent) => boolean);
  returnFocusOnDeactivate?: boolean;
  setReturnFocus?: HTMLElement | string | ((nodeFocusedBeforeActivation: HTMLElement) => HTMLElement | string);
  allowOutsideClick?: boolean | ((e: MouseEvent | TouchEvent) => boolean);
  preventScroll?: boolean;
  tabbableOptions?: TabbableOptions;
}

interface ActivateOptions {
  onActivate?: (focusTrapInstance: FocusTrap) => void;
}

interface DeactivateOptions {
  onDeactivate?: (focusTrapInstance: FocusTrap) => void;
  checkCanReturnFocus?: (trigger: HTMLElement) => Promise<void>;
}

Usage Examples:

import { useFocusTrap } from "@vueuse/integrations/useFocusTrap";
import { ref } from 'vue';

// Basic focus trap
const modalRef = ref<HTMLElement>();
const { activate, deactivate, hasFocus } = useFocusTrap(modalRef);

// Show modal with focus trap
const showModal = () => {
  // Show modal UI
  activate();
};

const closeModal = () => {
  deactivate();
  // Hide modal UI
};

// Multiple containers
const containers = [
  ref<HTMLElement>(),
  ref<HTMLElement>()
];

const { activate, deactivate } = useFocusTrap(containers, {
  immediate: false,
  escapeDeactivates: true,
  clickOutsideDeactivates: true
});

// Advanced configuration
const { activate, deactivate, pause, unpause } = useFocusTrap(modalRef, {
  initialFocus: '#first-input',
  fallbackFocus: '#cancel-button',
  onActivate: () => console.log('Focus trap activated'),
  onDeactivate: () => console.log('Focus trap deactivated'),
  escapeDeactivates: (e) => {
    // Custom logic for escape key
    return !e.shiftKey;
  },
  clickOutsideDeactivates: false
});

// Temporarily pause focus trap
const handleOverlay = () => {
  pause();
  // Handle overlay interaction
  setTimeout(unpause, 1000);
};

UseFocusTrap Component

Declarative focus trap component wrapper.

/**
 * Declarative focus trap component
 */
const UseFocusTrap = defineComponent({
  name: 'UseFocusTrap',
  props: {
    /** HTML tag to render */
    as: {
      type: [String, Object] as PropType<string | Component>,
      default: 'div'
    },
    /** Focus trap options */
    options: {
      type: Object as PropType<UseFocusTrapOptions>,
      default: () => ({})
    }
  },
  slots: {
    default: (props: {
      hasFocus: boolean;
      isPaused: boolean;
      activate: (opts?: ActivateOptions) => void;
      deactivate: (opts?: DeactivateOptions) => void;
      pause: Fn;
      unpause: Fn;
    }) => any;
  }
});

Component Usage:

<template>
  <UseFocusTrap 
    as="div" 
    :options="focusTrapOptions"
    v-slot="{ hasFocus, activate, deactivate }"
  >
    <div v-if="isModalOpen" class="modal">
      <h2>Modal Title</h2>
      <input ref="firstInput" placeholder="First input" />
      <input placeholder="Second input" />
      <button @click="closeModal">Close</button>
    </div>
  </UseFocusTrap>
</template>

<script setup>
import { UseFocusTrap } from "@vueuse/integrations/useFocusTrap";
import { ref } from 'vue';

const isModalOpen = ref(false);
const firstInput = ref<HTMLInputElement>();

const focusTrapOptions = {
  immediate: true,
  initialFocus: () => firstInput.value,
  escapeDeactivates: true
};

const closeModal = () => {
  isModalOpen.value = false;
};
</script>

Install with Tessl CLI

npx tessl i tessl/npm-vueuse--integrations

docs

form-validation.md

http-client.md

index.md

search-data.md

storage-state.md

ui-interactions.md

visual-effects.md

tile.json