shipshapecode / shipshapecode/shepherd

Improved advanceOn: Conditional inputs for touring through forms

Open
#3,442 1 comment 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
13.8k
Forks
658
Avg merge
4d 5h
Merged PRs (30d)
15

Description

Context

I have an application, where we are touring the user through forms.
It explains how to filter for data and how to submit queries.
Shepherd.js already allows some basic advancing using the advanceOn property;

{
  title: 'Click the button',
  buttons: [], // do not allow to advance by buttons
  advanceOn: {
    selector: '.my-form button',
    event: 'click'
  }
}

However, we have some steps, where I want to explain to the user that selecting a specific value in a <select/> or entering a specific text in an <input type="text"/>. That is not possible with advanceOn.

Workaround

Using when, it is possible to add custom triggers.
Example:

// event handlers
export const whenInputOnEnterValue = (
    elementSelector: string,
    expectedValue: string
): {
    show: () => void;
    hide: () => void;
} => {
    return {
        show: () => {
            createEventOnEnterInputValue(elementSelector, expectedValue);
        },
        hide: () => {
            removeEventOnEnterInputValue(elementSelector);
        }
    };
};

const createEventOnEnterInputValue = (elementSelector: string, expectedValue: string): void => {
    if (stepEventListener) {
        throw new Error('Event listener already created');
    }
    stepEventListener = (event) => {
        if (!(event.target instanceof HTMLInputElement)) {
            return;
        }

        if (event.target.value !== expectedValue) {
            return;
        }

        document.dispatchEvent(new Event('shepherd:next')); // Advancing using https://docs.shepherdjs.dev/guides/usage/#advancing-on-actions
    };
    document.querySelector(elementSelector)?.addEventListener('input', stepEventListener);
};

const removeEventOnEnterInputValue = (elementSelector: string): void => {
    document.querySelector(elementSelector)?.removeEventListener('input', stepEventListener!);
    stepEventListener = null;
};

// shepherd.js
    {
        attachTo: {
            element: '.my-form select#type',
            on: 'bottom'
        },
        canClickTarget: true,
        buttons: [],
        title: 'Select "Type 1" to continue',
        when: whenInputOnEnterValue('.my-form select#type', 'Example Type 1')
    },

This workaround is annoying for the following reasons;

  • Additional boilerplate code for registering events
  • Additional code that must be tested with unit tests
  • Doesn't reuse the advanceOn object.
  • We have automated bots that click through tours to ensure that they still work. The when property (or any callback for that matter) is really hard to test with. Static values in a property would be much easier.

[!NOTE]
The above example is simplified and does not compile for the sake of a minimal example.

Suggestion

It would be nice, if Shepherd would allow for more complex advanceOn, similar to the async beforeShowPromise().
A possible API implementation could be the following:

{
  title: 'Click the button',
  buttons: [], // do not allow to advance by buttons
  advanceOn: {
    selector: '.my-form select#type',
    elementEvent: 'change-select-option',
    optionValue: 'example-type-1' // Use TypeScript Unions to only allow this option when 'elementEvent' is set to 'change-select-option'
  }
}

Alternatively,

{
  title: 'Click the button',
  buttons: [], // do not allow to advance by buttons
  advanceAfter() {
    await waitForSelection('.my-form select#type', 'example-type-1');
  }
}
Environment

Node: v24
angular-shepherd@19.0.2
shepherd.js@15.2.2

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading Shepherd's existing advanceOn handling and its TypeScript option definitions, then compare them with the documented advancing-on-actions behavior and beforeShowPromise API. Determine which conditional input events and value checks are in scope from the proposal. Done means the chosen API is documented, typed, implemented, and covered by tests for the supported form interactions.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
frontend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.