shipshapecode / shipshapecode/shepherd
Improved advanceOn: Conditional inputs for touring through forms
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
advanceOnobject. - We have automated bots that click through tours to ensure that they still work. The
whenproperty (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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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