ember-sortable

repository·master·Indexed 43 days ago

https://github.com/adopted-ember-addons/ember-sortable

A library providing sortable UI primitives for Ember applications, enabling users to reorder lists via mouse dragging or keyboard navigation. It features modifiers for groups and items, support for x, y, and grid directions, built-in accessibility (a11y) settings, and acceptance test helpers for simulating drag-and-drop interactions.

Tokens
8.8K
Snippets
24
Records
40
Agent score
55%

What's inside ember-sortable

  1. How ember-sortable V2 components work together

    master

    The V2 architecture of ember-sortable is built using contextual components that follow a hierarchical structure. This design allows for composability, nesting, and customizability.

    There are three primary components:

    1. sortable-group: The top-level container. It manages the main sorting logic and requires a modelGroup (a shallow copy of the models to be sorted). It yields sortable-item and other properties.
    2. sortable-item: Represents an individual model within the group. It yields sortable-handle and other properties.
    3. sortable-handle: The entry point for sorting. This component connects a custom handle to the sortable-group mainframe. It is required to initiate drag or keyboard reordering.

    This structure supports nested sortable elements, meaning a sortable-group can be rendered inside a sortable-item of another group.

    {{#ember-sortable::sortable-group
      modelGroup=modelGroup
      onSubmit=(action "onSubmit")
      onDragStart=(action "onDragStart")
      onDragEnd=(action "onDragEnd")
      as |group|
    }}
      {{#each group.modelGroup as |singleModel index|}}
        {{#group.item
          model=singleModel
          index=index
          as |item|
        }}
          {{!-- You can nest another "sortable-group" here --}}
          {{!-- Content goes here --}}
          {{#item.handle}}
            {{!-- Handle goes here --}}
          {{/item.handle}}
        {{/group.item}}
      {{/each}}
    {{/ember-sortable::sortable-group}}
  2. Keyboard reordering workflow in V2

    master

    The V2 design implements an event-driven keyboard reordering workflow to ensure accessibility:

    1. Activation: When isKeyboardReorderModeEnabled is false, pressing ENTER or SPACE on a handle activates the reorder mode. The component switches to role="application" to manage focus.
    2. Navigation: Once active, use UP or DOWN arrow keys to move the selected item within the group. The component provides live region announcements for each move.
    3. Confirmation: Press ENTER or SPACE to confirm the new order. This invokes the onSubmit callback.
    4. Cancellation: Press ESC to discard changes and revert the item to its original position. This invokes the onSubmit callback with the original state (or reverts via the KeyboardReorderManager).
  3. Use sortable-handle as a modifier

    master

    To define a drag handle, apply the {{sortable-handle}} modifier to the specific element that should trigger the drag action. This replaces the old {{#item.handle}} block component.

    <!-- Old Component Syntax -->
    {{#sortable-group model=model.items onChange=(action "reorderItems") as |group|}}
      {{#each group.model as |modelItem|}}
        {{#group.item model=modelItem as|item|}}
          {{modelItem.name}}
          {{#item.handle}}
            <span class="handle">&varr;</span>
          {{/item.handle}}
        {{/group.item}}
      {{/each}}
    {{/sortable-group}}
    
    <!-- New Modifier Syntax -->
    <ol {{sortable-group onChange=(action 'reorderItems')}}>
      {{#each model.items as |item|}}
        <li {{sortable-item model=item}}>
          {{item.name}}
          <span class='handle' {{sortable-handle}}>&varr;</span>
        </li>
      {{/each}}
    </ol>
  4. Use sortable-item as a modifier

    master

    Each sortable item is now represented by applying the {{sortable-item}} modifier to a DOM element or component within the group. You pass the specific model for that item directly to the modifier.

    <!-- Old Component Syntax -->
    {{#sortable-group model=model.items onChange=(action 'reorderItems') as |group|}}
      {{#each group.model as |item|}}
        {{#group.item model=item}}
          {{item.name}}
        {{/group.item}}
      {{/each}}
    {{/sortable-group}}
    
    <!-- New Modifier Syntax -->
    <ol {{sortable-group onChange=(action 'reorderItems')}}>
      {{#each model.items as |item|}}
        <li {{sortable-item model=item}}>
          {{item.name}}
        </li>
      {{/each}}
    </ol>
  5. Use sortable-group as a modifier

    master

    Instead of using {{#sortable-group}} as a block component, apply {{sortable-group}} as a modifier to a container element (e.g., <ol> or <ul>).

    In the new modifier API, you no longer pass the model to the sortable-group modifier itself. Instead, you iterate over your models directly in the {{#each}} loop.

    <!-- Old Component Syntax -->
    {{#sortable-group model=model.items onChange=(action "reorderItems") as |group|}}
      {{#each group.model as |item|}}
        {{!-- ... --}}
      {{/each}}
    {{/sortable-group}}
    
    <!-- New Modifier Syntax -->
    <ol {{sortable-group onChange=(action "reorderItems")}}>
      {{#each model.items as |item|}}
        {{!-- ... --}}
      {{/each}}
    </ol>
  6. Use the draggable-group component for reordering items

    master

    The draggable-group component (proposed as drag-drop::draggable-group in V2) allows users to reorder a collection of models using both drag-and-drop and keyboard navigation. It is designed with accessibility in mind, providing screen reader announcements and keyboard support.

    Key Properties

    • modelGroup: The array of models to be rearranged.
    • itemName: A string name for the models, used to generate meaningful accessibility announcements (e.g., "Moving Item A").
    • onSubmit: A callback invoked when a reordering action is confirmed (via drop or keyboard Enter/Space).
    • onDragStart: A callback invoked when a drag operation begins.
    • onDragEnd: A callback invoked when a drag operation ends.
    • isKeyboardReorderModeEnabled: A boolean to enable/disable keyboard-based reordering.

    Usage Pattern

    The component uses a block helper pattern where it yields a hash containing an item component. You iterate over your modelGroup and wrap each item in the provided item component to enable drag functionality.

    {{#drag-drop::draggable-group
      onSubmit=(action onSubmit)
      modelGroup=modelGroup
      onDragStart=(action onDragStart)
      onDragEnd=(action onDragEnd)
      itemName=itemName
      as |group|
    }}
      {{#each group.modelGroup as |singleModel index|}}
        {{#group.item
          model=singleModel
          index=index
          as |item|
        }}
          {{singleModel}}
          {{#item.handle}}
            Handle
          {{/item.handle}}
        {{/group.item}}
      {{/each}}
    {{/drag-drop::draggable-group}}
  7. Use keyboard navigation for reordering in ember-sortable V2

    master

    Ember Sortable V2 includes built-in accessibility support for keyboard-based reordering.

    Keyboard Reorder Mode

    1. Initiate: Focus a sortable-handle and press Enter or Space. This selects the parent sortable-item and enables sorting within the sortable-group using the Up/Down/Left/Right arrow keys.
    2. Visual Indicators: While in reorder mode, a visual indicator (defaulting to arrows via CSS classes) will appear around the selected item and move as the item is reordered.
    3. Commit: Press Enter or Space again to commit the new order. Focus will move from the sortable-group back to the sortable-handle.
    4. Reset: Press Escape or lose focus to exit reorder mode. This resets the UI to the initial state and retains focus on the sortable-handle.

    Screen Reader Support

    Sorting actions are announced to screen readers. Consumers can provide a custom announceActionConfig object to map specific actions to translated text strings for i18n support.

  8. Use ember-sortable acceptance test helpers

    master

    For testing sortable functionality, ember-sortable provides acceptance test helpers to simulate user interactions like dragging, reordering, and keyboard input. These helpers are available via the ember-sortable/test-support entry point.

    Available helpers:

    • drag: Drags elements by a pixel offset.
    • reorder: Reorders elements to a specific target state.
    • keyboard: Provides keycode constants for keyboard simulation.
    import { 
      drag, 
      reorder,
      ENTER_KEY_CODE,
      SPACE_KEY_CODE,
      ESCAPE_KEY_CODE,
      ARROW_KEY_CODES,
    } from 'ember-sortable/test-support';
    
    // Example: Reorder elements
    await reorder('mouse', '[data-test-vertical-demo-handle]', ...order);
    
    // Example: Drag an element with a specific offset
    await drag('mouse', '[data-test-scrollable-demo-handle] .handle', () => {
      return { dy: itemHeight() * 2 + 1, dx: undefined };
    });
    
    // Example: Simulate keyboard interaction
    await triggerKeyEvent('[data-test-vertical-demo-handle]', 'keydown', ENTER_KEY_CODE);
  9. Migrate to ember-sortable v3

    master

    When migrating from version 2 to version 3:

    • Components: Component-based usage has been removed. You must now use the modifier approach.
    • groupModel: The modifier version does not support groupModel directly. Instead, use the currying of the fn helper to achieve similar functionality.