diff --git a/.circleci/config.yml b/.circleci/config.yml
index 79f0eac7c4..2a4c10800d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -10,7 +10,7 @@ executors:
parameters:
current_golden_images_hash:
type: string
- default: 824fb67e89d53a25416b315fc8dda02aa83e5482
+ default: b7e5c09d7b9eb5384d503cd9a4409f757a549115
wireit_cache_name:
type: string
default: wireit
diff --git a/packages/overlay/stories/overlay.stories.ts b/packages/overlay/stories/overlay.stories.ts
index f8a1fd764c..ee68f52f43 100644
--- a/packages/overlay/stories/overlay.stories.ts
+++ b/packages/overlay/stories/overlay.stories.ts
@@ -173,6 +173,7 @@ const template = ({
min="0"
max="20"
label="Awesomeness"
+ default-value="10"
>
The background of this div should be blue
diff --git a/packages/slider/README.md b/packages/slider/README.md
index 23381353e7..5eb9b2905c 100644
--- a/packages/slider/README.md
+++ b/packages/slider/README.md
@@ -232,6 +232,19 @@ An `` element can be paired with an `` element via t
```
+#### Default value
+
+Slider will reset to its `default-value` when the user double clicks on the slider handle or if the user presses the `escape` key when the slider handle is focused.
+
+```html
+
+```
+
+Note: If a slider with `default-value` attribute is contained in a modal and the slider-handle is focused then the following interaction will occur on pressing the `escape` key:
+
+- If the slider value is different from the default value then the slider value will be reset to the default value and the modal will not be closed.
+- If the slider value is equal to the default value then the modal will be closed.
+
#### Indeterminate
The indeterminate attribute will be passed to the internal `` element and alter its visual delivery until a change has been made to the `` element at which point the `change` event that is dispatched can be understood as always removing the indeterminate attribute from the ``.
diff --git a/packages/slider/src/HandleController.ts b/packages/slider/src/HandleController.ts
index 279bf21042..ffc7195150 100644
--- a/packages/slider/src/HandleController.ts
+++ b/packages/slider/src/HandleController.ts
@@ -341,6 +341,23 @@ export class HandleController {
private _activePointerEventData!: DataFromPointerEvent | undefined;
+ /**
+ * @description check for defaultvalue(value) property in sp-slider and reset on double click on sliderHandle
+ * @param event
+ */
+ public handleDoubleClick(event: PointerEvent): void {
+ const input = (event.target as Element).querySelector(
+ '.input'
+ ) as InputWithModel;
+
+ if (input.model?.handle.defaultValue !== undefined) {
+ input.model.handle.value = input.model.handle.defaultValue;
+ this.dispatchChangeEvent(input, input.model.handle);
+ input.model.handle.dispatchInputEvent();
+ this.requestUpdate();
+ }
+ }
+
public handlePointerdown(event: PointerEvent): void {
const { resolvedInput, model } = this.extractDataFromEvent(event);
if (!model || this.host.disabled || event.button !== 0) {
@@ -432,7 +449,22 @@ export class HandleController {
this.requestUpdate();
};
- private onInputKeydown = (event: Event): void => {
+ private onInputKeydown = (event: KeyboardEvent): void => {
+ if (event.key == 'Escape') {
+ const input = event.target as InputWithModel;
+ if (
+ input.model.handle?.defaultValue !== undefined &&
+ input.model.handle.value !== input.model.handle.defaultValue
+ ) {
+ input.model.handle.value = input.model.handle.defaultValue;
+ input.model.handle.dispatchInputEvent();
+ this.dispatchChangeEvent(input, input.model.handle);
+ this.requestUpdate();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ return;
+ }
const input = event.target as InputWithModel;
input.model.handle.highlight = true;
this.requestUpdate();
@@ -522,12 +554,17 @@ export class HandleController {
aria-label=${ifDefined(model.ariaLabel)}
aria-labelledby=${ariaLabelledBy}
aria-valuetext=${this.formattedValueForHandle(model)}
+ aria-describedby="slider-description"
@change=${this.onInputChange}
@focus=${this.onInputFocus}
@blur=${this.onInputBlur}
@keydown=${this.onInputKeydown}
.model=${model}
/>
+
+ Press escape or double click to reset the slider to its
+ default value.
+
`;
}
diff --git a/packages/slider/src/Slider.ts b/packages/slider/src/Slider.ts
index 2c6cf8563f..817f428f99 100644
--- a/packages/slider/src/Slider.ts
+++ b/packages/slider/src/Slider.ts
@@ -362,7 +362,6 @@ export class Slider extends SizedMixin(ObserveSlotText(SliderHandle, ''), {
* @description calculates the fill width
* @param fillStartValue
* @param currentValue
- * @param cachedValue
* @returns
*/
private getOffsetWidth(
@@ -409,10 +408,12 @@ export class Slider extends SizedMixin(ObserveSlotText(SliderHandle, ''), {
>
`;
}
-
private renderHandle(): TemplateResult {
+ if (this.variant === 'tick') {
+ return html``;
+ }
return html`
- ${this.variant === 'tick' ? html`` : this.handleController.render()}
+ ${this.handleController.render()}
`;
}
@@ -442,6 +443,7 @@ export class Slider extends SizedMixin(ObserveSlotText(SliderHandle, ''), {
['pointerup', 'pointercancel', 'pointerleave'],
this.handlePointerup,
],
+ streamOutside: ['dblclick', this.handleDoubleClick],
})}
>
@@ -475,6 +477,10 @@ export class Slider extends SizedMixin(ObserveSlotText(SliderHandle, ''), {
`;
}
+ protected handleDoubleClick(event: PointerEvent): void {
+ this.handleController.handleDoubleClick(event);
+ }
+
protected handlePointerdown(event: PointerEvent): void {
this.handleController.handlePointerdown(event);
}
diff --git a/packages/slider/src/SliderHandle.ts b/packages/slider/src/SliderHandle.ts
index d680e02c32..100d567589 100644
--- a/packages/slider/src/SliderHandle.ts
+++ b/packages/slider/src/SliderHandle.ts
@@ -92,6 +92,13 @@ export class SliderHandle extends Focusable {
@property({ type: Number })
value!: number;
+ /**
+ * Set the default value of the handle. Setting this property will cause the
+ * handle to reset to the default value on double click or pressing the `escape` key.
+ */
+ @property({ type: Number, attribute: 'default-value' })
+ defaultValue!: number;
+
@property({ type: Boolean, reflect: true })
public dragging = false;
diff --git a/packages/slider/src/slider.css b/packages/slider/src/slider.css
index cc8175be54..3633e926fe 100644
--- a/packages/slider/src/slider.css
+++ b/packages/slider/src/slider.css
@@ -149,3 +149,7 @@ governing permissions and limitations under the License.
.fill {
z-index: 2;
}
+
+#slider-description {
+ display: none;
+}
diff --git a/packages/slider/stories/slider.stories.ts b/packages/slider/stories/slider.stories.ts
index 76b9b76219..1629652b12 100644
--- a/packages/slider/stories/slider.stories.ts
+++ b/packages/slider/stories/slider.stories.ts
@@ -152,6 +152,26 @@ export const Filled = (args: StoryArgs = {}): TemplateResult => {
`;
};
+export const HasADefaultValue = (args: StoryArgs = {}): TemplateResult => {
+ return html`
+
+
+ double click or press escape key to reset
+
+
+ `;
+};
+
export const FillStart = (args: StoryArgs = {}): TemplateResult => {
return html`
@@ -397,6 +417,37 @@ export const editable = (args: StoryArgs = {}): TemplateResult => {
editable.decorators = [editableDecorator];
+export const editableWithDefaultValue = (
+ args: StoryArgs = {}
+): TemplateResult => {
+ return html`
+
+
+ Angle
+
+
+ `;
+};
+
+editableWithDefaultValue.swc_vrt = {
+ skip: true,
+};
+
export const editableDisabled = (args: StoryArgs = {}): TemplateResult => {
return html`
diff --git a/packages/slider/test/index.ts b/packages/slider/test/index.ts
index 4a24bc80e6..68c3ca0b2e 100644
--- a/packages/slider/test/index.ts
+++ b/packages/slider/test/index.ts
@@ -310,6 +310,7 @@ export const testEditableSlider = (type: string): void => {
pointerType: 'mouse',
})
);
+
await elementUpdated(el);
expect(el.dragging, 'it is dragging 1').to.be.true;
@@ -340,6 +341,7 @@ export const testEditableSlider = (type: string): void => {
pointerType: 'mouse',
})
);
+
await elementUpdated(el);
expect(el.dragging, 'it is dragging 2').to.be.true;
diff --git a/packages/slider/test/slider.test.ts b/packages/slider/test/slider.test.ts
index 68139c2248..a0258aec5c 100644
--- a/packages/slider/test/slider.test.ts
+++ b/packages/slider/test/slider.test.ts
@@ -12,6 +12,10 @@ governing permissions and limitations under the License.
import '@spectrum-web-components/slider/sp-slider.js';
import '@spectrum-web-components/slider/sp-slider-handle.js';
+import '@spectrum-web-components/button/sp-button.js';
+import '@spectrum-web-components/overlay/sp-overlay.js';
+import '@spectrum-web-components/popover/sp-popover.js';
+import { Overlay } from '@spectrum-web-components/overlay';
import { Slider, SliderHandle } from '@spectrum-web-components/slider';
import { tick } from '../stories/slider.stories.js';
import {
@@ -169,7 +173,6 @@ describe('Slider', () => {
})
);
await elementUpdated(el);
-
expect(el.dragging, 'it is dragging 1').to.be.true;
expect(pointerId, '2').to.equal(1);
@@ -1604,4 +1607,161 @@ describe('Slider', () => {
await elementUpdated(el);
expect(el.values).to.deep.equal({ a: 10, b: 20, c: 29 });
});
+ it('resets to default value on double click after moving pointer', async () => {
+ const inputSpy = spy();
+ const changeSpy = spy();
+
+ const el = await fixture
(
+ html`
+ {
+ inputSpy(event.target.value);
+ }}
+ @change=${(event: Event & { target: Slider }) => {
+ changeSpy(event.target.value);
+ }}
+ >
+ `
+ );
+ await elementUpdated(el);
+ expect(el.value, 'initial').to.equal(50);
+
+ const handle = el.shadowRoot.querySelector('.handle') as HTMLDivElement;
+ const handleBoundingRect = handle.getBoundingClientRect();
+ const position: [number, number] = [
+ handleBoundingRect.x + handleBoundingRect.width / 2,
+ handleBoundingRect.y + handleBoundingRect.height / 2,
+ ];
+ await sendMouse({
+ steps: [
+ {
+ type: 'move',
+ position,
+ },
+ {
+ type: 'down',
+ },
+ ],
+ });
+
+ await elementUpdated(el);
+ await sendMouse({
+ steps: [
+ {
+ type: 'move',
+ position: [
+ 150,
+ handleBoundingRect.y + handleBoundingRect.height + 100,
+ ],
+ },
+ {
+ type: 'up',
+ },
+ ],
+ });
+
+ await elementUpdated(el);
+
+ // since we've moved the pointer, the new value should be 100
+ expect(el.value).to.equal(100);
+
+ inputSpy.resetHistory();
+ changeSpy.resetHistory();
+
+ handle.dispatchEvent(
+ new PointerEvent('dblclick', {
+ clientX: 0,
+ cancelable: true,
+ button: 0,
+ composed: true,
+ bubbles: true,
+ })
+ );
+
+ await elementUpdated(el);
+
+ expect(
+ el.value,
+ 'reset to default value on double click after moving pointer'
+ ).to.equal(50);
+
+ // input and change events should have been fired
+ expect(inputSpy.callCount).to.equal(1);
+ expect(changeSpy.callCount).to.equal(1);
+ });
+ it('manages escape key interactions correctly in an overlaid context', async () => {
+ const inputSpy = spy();
+ const changeSpy = spy();
+
+ const el = await fixture(
+ html`
+
+ Overlay Trigger
+
+
+ {
+ inputSpy(event.target.value);
+ }}
+ @change=${(
+ event: Event & { target: Slider }
+ ) => {
+ changeSpy(event.target.value);
+ }}
+ >
+
+
+
+ `
+ );
+
+ await elementUpdated(el);
+
+ // open the overlay
+ const trigger = el.querySelector('#trigger') as HTMLButtonElement;
+ const opened = oneEvent(el, 'sp-opened');
+ trigger.click();
+ await opened;
+
+ // current slider value should be 70
+ const slider = el.querySelector('sp-slider') as Slider;
+ expect(slider.value).to.equal(70);
+
+ slider.focus();
+ // send escape key
+ await sendKeys({
+ press: 'Escape',
+ });
+
+ await elementUpdated(el);
+
+ // now the slider value should be 50
+ expect(slider.value).to.equal(50);
+
+ // input and change events should have been fired
+ expect(inputSpy.callCount).to.equal(1);
+ expect(changeSpy.callCount).to.equal(1);
+
+ // and the overlay should be in open state
+ const overlay = el.querySelector('sp-overlay') as Overlay;
+ expect(overlay.open).to.be.true;
+
+ const closed = oneEvent(el, 'sp-closed');
+ // send escape key again
+ await sendKeys({
+ press: 'Escape',
+ });
+ await closed;
+
+ // now the overlay should be closed
+ expect(overlay.open).to.be.false;
+ });
});