-
Notifications
You must be signed in to change notification settings - Fork 211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[range-slider] #1385
Comments
This sounds great. Thanks for bringing this information together! Do you think that you or your team will be able to contribute something like this? Label FormattingWe should make sure to take into account #1159 when thinking about the label formatting approach. Having the general API be as close to the standard There's also the possibility that we will need specific formatting for the value of each of the handles, as they will be their own tab stops and their own <div class="spectrum-Slider spectrum-Slider--range" role="group" aria-labelledby="spectrum-Slider-label-4">
<div class="spectrum-Slider-labelContainer" role="presentation">
<label class="spectrum-Slider-label" id="spectrum-Slider-label-4" for="spectrum-Slider-input-4-0">Slider Label</label>
<div class="spectrum-Slider-value" role="textbox" aria-readonly="true" aria-labelledby="spectrum-Slider-label-4">14 - 48</div>
</div>
<div class="spectrum-Slider-controls" role="presentation">
<div class="spectrum-Slider-track" style="width: 20%;"></div>
<div class="spectrum-Slider-handle" style="left: 20%;" role="presentation">
<input type="range" class="spectrum-Slider-input" value="14" step="2" min="10" max="20" aria-label="min" id="spectrum-Slider-input-4-0" aria-labelledby="spectrum-Slider-label-4 spectrum-Slider-input-4-0">
</div>
<div class="spectrum-Slider-track" style="left: 20%; right: 40%;"></div>
<div class="spectrum-Slider-handle" style="left: 60%;" role="presentation">
<input type="range" class="spectrum-Slider-input" value="14" step="2" min="10" max="20" aria-label="max" id="spectrum-Slider-input-4-1" aria-labelledby="spectrum-Slider-label-4 spectrum-Slider-input-4-1">
</div>
<div class="spectrum-Slider-track" style="width: 40%;"></div>
</div>
</div> In reference to the above, a having curtom label formatting for the start/end values would support an implementor choosing to inform the screen reader to read something like ValueFor the Min/Max/DefaultsIt would seem that |
One other thing to think about in regards to this API shape: |
So, I am working on this implementation right now. So, yes we can contribute. We need this yesterday 😄 Here's what I am playing with to support multiple stops in a way that allows expansion and customization. As I mentioned, the first thing I will do with this is customize it to add a third stop for a particular use case. I am am proposing splitting protected abstract valueForHandle(handle: HTMLElement): number;
protected abstract setValueForHandle(name: string, value: number): void; That would allow the implementation to get/set the value for a handle. Those methods would interact with the value attributes such as Internally, I am looking at the concept of a handle stack. Handles should stack in z based on how recently they have been interacted with. So, a handle that you have just dragged should be on top of other handles. Here's what the implementation I am playing with looks like: export type Handle = {
handle: HTMLDivElement;
input: HTMLInputElement;
};
export type HandleOrEmpty = {
handle?: HTMLDivElement;
input?: HTMLInputElement;
}
export class HandleStack<HandleNames extends string> {
private handles: Handle[] = [];
constructor(root: ShadowRoot) {
const inputs = root.querySelectorAll('.handle > input[type="range"]');
this.handles = Array.from(inputs).map(input => {
const inputElem = input as HTMLInputElement;
const handleElem = inputElem.parentElement as HTMLDivElement;
return {
handle: handleElem,
input: inputElem,
};
});
}
public get activeHandle(): Handle {
return this.handles[this.handles.length - 1];
}
public get activeHandleName(): string {
const { handle } = this.handles[this.handles.length - 1];
return handle.id;
}
public activateHandle(handleElement: HTMLDivElement): void {
const index = this.handles.findIndex(item => item.handle === handleElement);
if (index > 0) {
const handle = this.handles[index];
this.handles.splice(index, 1);
this.handles.push(handle);
}
}
public handleByName(name: HandleNames): HandleOrEmpty {
return this.handles.find(item => item.handle.id === name) as Handle ?? {};
}
public indexOfHandle(name: HandleNames): number {
for (let index = 0; index < this.handles.length; index++) {
const item = this.handles[index];
if (item.handle.id === name) {
return index;
}
}
return 0;
}
} The handle stack gets the handle divs and their inputs by doing a selector query, so it will pick up new handles added by subclassed implementations. |
I like the structure here for managing handles. Could you help me to better understand how you'd use it in over the life of the element? Particularly, how you'd see it specifically powering the API for 2 or more handles at the For instance what would the DOM/attribute interface be as you scale up the number of handles based off of the named access?
I'd much prefer constructing the stack from data, rather than reconstructing the stack from DOM. It may be that we leverage the stack to build the DOM? Something along the lines of the following pseudo code: class RangeSlider extends SliderBase {
private handleStack = new HandleStack(this, 2);
render() {
return html`
// ...
${this.handleStack.render(listeners, whatever)}
// ...
`;
}
}
export class HandleStack<HandleNames extends string> {
render() {
return this.handles.map(this.renderHandle);
}
renderHandle(handle, index) {
return html`
<div class="handle" role="presentation" style="left: ${this.asPercent(handle.value)}; z-index: ${index + 1};">
<input type="range" id="input" value="${handle.value}" step="etc" min="etc" max="etc" aria-valuetext="${this.asPercent(handle.value)}">
</div>
`;
}
} |
Hmm, I like the idea of constructing the stack and rendering the handles from that... Thanks for that input.. For the declarative API idea. That also sounds interesting. How would we specify the interactions between handles? In some cases, the handles must be strictly ordered and in some they can slide past each other. I can think of examples of both cases... <sp-slider enforce-handle-order>
<sp-slider-handle name="x" value="0"></sp-slider-handle>
<sp-slider-handle name="y" value="1"></sp-slider-handle>
<sp-slider-handle name="z" value="3"></sp-slider-handle>
</sp-slider> Something like this? Only with a better name than |
Definitely could work. Alternative API might be to include min/max on each handle, types to <sp-slider min=0 max=10>
<sp-slider-handle name="x" value="0" min="0" max="next"></sp-slider-handle>
<sp-slider-handle name="y" value="1" min="0" max="8"></sp-slider-handle>
<sp-slider-handle name="z" value="3" min="previous" max="10"></sp-slider-handle>
</sp-slider> One case to think about is whether you support one handle moving another. For instance, if you added something like a What's the use case on the order that can change? I definitely could see your API supporting the two options, but I'd wonder are we actually delivering a quality UX for user in that context. Seems a bit confusing, but maybe the context it's relative to give it light there? |
I do not currently have a use case for one slider moving another, although I can imagine there might be a use case. So, I would like to leave that as work for whoever hits that case, but we shouldn't design an API that could not extend to support it. One oddity I do have is a three-slider example where the middle slider maintains proportional spacing as you move the outer sliders. I presume I could custom code that with events. |
One other thing I need is a way to specify the background colour of the handles. I know that is outside of Spectrum proper, but it's necessary in my current UI... |
I really like how this is coming together, please let me know if there is anything I can do to support! VariantsIn not supporting variants (at least for now), would the idea be that having more than one Label formattingI like keeping the changes here as slim as possible, but I have started to work with a
|
VariantsI had imagined that the root element (the Label formattingI like the combination of the Values propertyI can try to make the |
I bet there'a a nice way to leverage the @adixon-adobe Does this get outlined in Spectrum specifically, or are is the education later something your team is adding with less central input? |
It's eluded to here https://spectrum-contributions.corp.adobe.com/page/tutorial-coach-mark-beta/ but the impact to slider isn't specifically called out. |
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
Implement multi-handle slider support as discussed in #1385
This shipped 🎊 ...a while back. Thanks again @cuberoot!! For anyone who participated in the conversation above, there may be some nice nuggets for extension in the notes above, so feel free to open new issues as you see fit. |
Multi-Handle Slider Proposal
This began as a proposal for an implementation of the range slider as documented in Spectrum Contributions and as implemented in spectrum-css.
The discussion (see below) has led us to a more general extension to sliders to allow multiple handles. This is the updated reworked proposal.
The basic gist of the proposal is to allow the user to specify the handles explicitly using an
sp-slider-handle
element as per this example:Node that in the absence of specified name, the handles will be named
handle1
,handle2
, etc.Backwards compatibility and the simple case
In the case where only one handle is needed, we will read the
value
,min
andmax
properties off of thesp-slider
itself. It will act as thesp-slider-handle
.Variants
It is likely that we will only support the default variant when there is more than one handle.
Handle clamping
In most cases, but not all, we will want to enforce the ordering of the handles. When ordering is enforced, handles will not be able to move past each other. The API will take the form of
min
andmax
properties on the handles.min
'previous'
|number
|undefined
max
'next'
|number
|undefined
The values
previous
andnext
will prevent a handle from moving past the adjacent handle in that direction.Label formatting
Currently we support a
getAriaValueText
property which is a function that does the formatting. Our solution need to take into account #1159. See comments below. This section needs to be fleshed out further.Each
sp-slider-handle
and thesp-slider
will have a property like this:The
getAriaValueText
property will be modified to take a function of type(values: Map<string, string>) => string
. In the case where there is only one slider, the map will just contain the the pair'value' => <value of slider
. The string values will be the individual slider values by name as formatted according to theirformatOptions
. If ansp-slider-handle
does not have a value forformatOptions
than theformatOptions
from the hostsp-slider
will be used.Question: When there is only one implicit
sp-slider-handle
, should we just pass the value as a string instead of in aMap
? Is aMap
the right structure. I chose it because it is both random access and ordered.Events
The events should look the same as for the standard slider with
change
andinput
events. The change and input events will be per-handle and will come from thesp-slider-handle
element. Where there are no explicitly declaredsp-slider-handle
elements (there is only one default handle), the events will come from thesp-slider
itself.values
propertyThe
value
property that will return an array of objects with thename
andvalue
for each handle such as:Interactions
From playing with other implementations, it appears that the most recently dragged handle is always on top. The keyboard should interact with the currently selected handle. And you should be able to tab between handles.
Breaking changes
sp-slider-handle
children.getAriaValueText
function property will now be passed aMap<string, string>
with the handle names and values.The text was updated successfully, but these errors were encountered: