Bug 1478156 - Make color picker tooltip keyboard accessible, r=yzen,gl

Differential Revision: https://phabricator.services.mozilla.com/D33331

--HG--
rename : devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js => devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click-or-keyboard-activation.js
extra : moz-landing-system : lando
This commit is contained in:
Maliha Islam 2019-07-15 11:01:04 +00:00
parent 8cfcbe667f
commit 7bd7b72040
14 changed files with 458 additions and 144 deletions

View File

@ -81,7 +81,7 @@ skip-if = (verify && debug && os == 'win')
[browser_rules_class_panel_toggle.js]
[browser_rules_colorpicker-and-image-tooltip_01.js]
[browser_rules_colorpicker-and-image-tooltip_02.js]
[browser_rules_colorpicker-appears-on-swatch-click.js]
[browser_rules_colorpicker-appears-on-swatch-click-or-keyboard-activation.js]
[browser_rules_colorpicker-commit-on-ENTER.js]
[browser_rules_colorpicker-edit-gradient.js]
[browser_rules_colorpicker-hides-element-picker.js]
@ -90,6 +90,7 @@ skip-if = (verify && debug && os == 'win')
[browser_rules_colorpicker-release-outside-frame.js]
[browser_rules_colorpicker-revert-on-ESC.js]
[browser_rules_colorpicker-swatch-displayed.js]
[browser_rules_colorpicker-wrap-focus.js]
[browser_rules_colorUnit.js]
[browser_rules_completion-existing-property_01.js]
[browser_rules_completion-existing-property_02.js]

View File

@ -4,7 +4,7 @@
"use strict";
// Tests that color pickers appear when clicking on color swatches.
// Tests that color pickers appear when clicking or using keyboard on color swatches.
const TEST_URI = `
<style type="text/css">
@ -26,13 +26,23 @@ add_task(async function() {
for (const property of propertiesToTest) {
info("Testing that the colorpicker appears on swatch click");
const value = getRuleViewProperty(view, "body", property).valueSpan;
const swatch = value.querySelector(".ruleview-colorswatch");
await testColorPickerAppearsOnColorSwatchClick(view, swatch);
await testColorPickerAppearsOnColorSwatchActivation(view, property);
info(
"Testing that swatch is focusable and colorpicker can be activated with a keyboard"
);
await testColorPickerAppearsOnColorSwatchActivation(view, property, true);
}
});
async function testColorPickerAppearsOnColorSwatchClick(view, swatch) {
async function testColorPickerAppearsOnColorSwatchActivation(
view,
property,
withKeyboard = false
) {
const value = getRuleViewProperty(view, "body", property).valueSpan;
const swatch = value.querySelector(".ruleview-colorswatch");
const cPicker = view.tooltips.getTooltip("colorPicker");
ok(cPicker, "The rule-view has the expected colorPicker property");
@ -40,7 +50,20 @@ async function testColorPickerAppearsOnColorSwatchClick(view, swatch) {
ok(cPickerPanel, "The XUL panel for the color picker exists");
const onColorPickerReady = cPicker.once("ready");
swatch.click();
if (withKeyboard) {
// Focus on the property value span
const doc = value.ownerDocument;
value.focus();
// Tab to focus on the color swatch
EventUtils.sendKey("Tab");
is(doc.activeElement, swatch, "Swatch successfully receives focus.");
// Press enter on the swatch to simulate click and open color picker
EventUtils.sendKey("Return");
} else {
swatch.click();
}
await onColorPickerReady;
ok(true, "The color picker was shown on click of the color swatch");

View File

@ -0,0 +1,77 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that focus stays inside color picker on TAB and Shift + TAB
const TEST_URI = `
<style type="text/css">
body {
color: red;
background-color: #ededed;
background-image: url(chrome://global/skin/icons/warning-64.png);
border: 2em solid rgba(120, 120, 120, .5);
}
</style>
Testing the color picker tooltip!
`;
add_task(async function() {
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
const { view } = await openRuleView();
info("Focus on the property value span");
getRuleViewProperty(view, "body", "color").valueSpan.focus();
const cPicker = view.tooltips.getTooltip("colorPicker");
const onColorPickerReady = cPicker.once("ready");
info(
"Tab to focus on the color swatch and press enter to simulate a click event"
);
EventUtils.sendKey("Tab");
EventUtils.sendKey("Return");
await onColorPickerReady;
const doc = cPicker.spectrum.element.ownerDocument;
ok(
doc.activeElement.classList.contains("spectrum-color"),
"Focus is initially on the spectrum dragger when color picker is shown."
);
info("Test that tabbing should move focus to the next focusable elements.");
testFocusOnTab(doc, "devtools-button");
testFocusOnTab(doc, "spectrum-hue-input");
testFocusOnTab(doc, "spectrum-alpha-input");
testFocusOnTab(doc, "learn-more");
info(
"Test that tabbing on the last element wraps focus to the first element."
);
testFocusOnTab(doc, "spectrum-color");
info(
"Test that shift tabbing on the first element wraps focus to the last element."
);
testFocusOnTab(doc, "learn-more", true);
info(
"Test that shift tabbing should move focus to the previous focusable elements."
);
testFocusOnTab(doc, "spectrum-alpha-input", true);
testFocusOnTab(doc, "spectrum-hue-input", true);
testFocusOnTab(doc, "devtools-button", true);
testFocusOnTab(doc, "spectrum-color", true);
await hideTooltipAndWaitForRuleViewChanged(cPicker, view);
});
function testFocusOnTab(doc, expectedClass, shiftKey = false) {
EventUtils.synthesizeKey("VK_TAB", { shiftKey });
ok(
doc.activeElement.classList.contains(expectedClass),
"Focus is on the correct element."
);
}

View File

@ -56,6 +56,15 @@ async function testEditableFieldFocus(
const propEditor = textProp.editor;
await focusNextField(view, ruleEditor, commitKey, options);
if (
["background-color", "color"].includes(propEditor.nameSpan.textContent)
) {
// background-color and color property value spans have inner focusable elements
// and so, focus needs to move to the inplace editor field where enter needs to be
// pressed to trigger click event on it
await focusNextField(view, ruleEditor, commitKey, options);
EventUtils.sendKey("Return");
}
await assertEditor(
view,
propEditor.valueSpan,

View File

@ -35,6 +35,12 @@ loader.lazyRequireGetter(
"devtools/shared/css/parsing-utils",
true
);
loader.lazyRequireGetter(
this,
"findCssSelector",
"devtools/shared/inspector/css-logic",
true
);
const HTML_NS = "http://www.w3.org/1999/xhtml";
@ -497,6 +503,12 @@ TextPropertyEditor.prototype = {
};
}
// Save focused element inside value span if one exists before wiping the innerHTML
let focusedElSelector = null;
if (this.valueSpan.contains(this.doc.activeElement)) {
focusedElSelector = findCssSelector(this.doc.activeElement);
}
this.valueSpan.innerHTML = "";
this.valueSpan.appendChild(frag);
@ -681,6 +693,14 @@ TextPropertyEditor.prototype = {
// Update the rule property highlight.
this.ruleView._updatePropertyHighlight(this);
// Restore focus back to the element whose markup was recreated above.
if (focusedElSelector) {
const elementToFocus = this.doc.querySelector(focusedElSelector);
if (elementToFocus) {
elementToFocus.focus();
}
}
},
/* eslint-enable complexity */

View File

@ -205,6 +205,10 @@ function editableItem(options, callback) {
element.addEventListener(
"keypress",
function(evt) {
if (evt.target.nodeName === "button") {
return;
}
if (isKeyIn(evt.keyCode, "RETURN") || isKeyIn(evt.charCode, "SPACE")) {
callback(element);
}

View File

@ -58,6 +58,8 @@ const BASIC_SHAPE_FUNCTIONS = ["polygon", "circle", "ellipse", "inset"];
const BACKDROP_FILTER_ENABLED = Services.prefs.getBoolPref(
"layout.css.backdrop-filter.enabled"
);
const SHARED_SWATCH_CLASS = "ruleview-swatch";
const COLOR_SWATCH_CLASS = "ruleview-colorswatch";
const HTML_NS = "http://www.w3.org/1999/xhtml";
@ -1483,10 +1485,16 @@ OutputParser.prototype = {
});
if (options.colorSwatchClass) {
const swatch = this._createNode("span", {
class: options.colorSwatchClass,
style: "background-color:" + color,
});
const swatch = this._createNode(
options.colorSwatchClass ===
`${SHARED_SWATCH_CLASS} ${COLOR_SWATCH_CLASS}`
? "button"
: "span",
{
class: options.colorSwatchClass,
style: "background-color:" + color,
}
);
this.colorSwatches.set(swatch, colorObj);
swatch.addEventListener("mousedown", this._onColorSwatchMouseDown);
EventEmitter.decorate(swatch);

View File

@ -35,7 +35,7 @@ add_task(async function() {
await testCreateAndDestroyShouldAppendAndRemoveElements(container);
await testPassingAColorAtInitShouldSetThatColor(container);
await testSettingAndGettingANewColor(container);
await testChangingColorShouldEmitEvents(container);
await testChangingColorShouldEmitEvents(container, doc);
await testSettingColorShoudUpdateTheUI(container);
await testChangingColorShouldUpdateColorPreview(container);
await testNotSettingTextPropsShouldNotShowContrastSection(container);
@ -144,26 +144,88 @@ function testSettingAndGettingANewColor(container) {
s.destroy();
}
function testChangingColorShouldEmitEvents(container) {
return new Promise(resolve => {
const s = new Spectrum(container, cssColors.white);
s.show();
s.once("changed", (rgba, color) => {
ok(true, "Changed event was emitted on color change");
is(rgba[0], 128, "New color is correct");
is(rgba[1], 64, "New color is correct");
is(rgba[2], 64, "New color is correct");
is(rgba[3], 1, "New color is correct");
is(`rgba(${rgba.join(", ")})`, color, "RGBA and css color correspond");
s.destroy();
resolve();
});
// Simulate a drag move event by calling the handler directly.
s.onDraggerMove(s.dragger.offsetWidth / 2, s.dragger.offsetHeight / 2);
async function testChangingColorShouldEmitEventsHelper(
spectrum,
moveFn,
expectedColor
) {
const onChanged = spectrum.once("changed", (rgba, color) => {
is(rgba[0], expectedColor[0], "New color is correct");
is(rgba[1], expectedColor[1], "New color is correct");
is(rgba[2], expectedColor[2], "New color is correct");
is(rgba[3], expectedColor[3], "New color is correct");
is(`rgba(${rgba.join(", ")})`, color, "RGBA and css color correspond");
});
moveFn();
await onChanged;
ok(true, "Changed event was emitted on color change");
}
function testChangingColorShouldEmitEvents(container, doc) {
const s = new Spectrum(container, cssColors.white);
s.show();
const sendUpKey = () => EventUtils.sendKey("Up");
const sendDownKey = () => EventUtils.sendKey("Down");
const sendLeftKey = () => EventUtils.sendKey("Left");
const sendRightKey = () => EventUtils.sendKey("Right");
info(
"Test that simulating a mouse drag move event emits color changed event"
);
const draggerMoveFn = () =>
s.onDraggerMove(s.dragger.offsetWidth / 2, s.dragger.offsetHeight / 2);
testChangingColorShouldEmitEventsHelper(s, draggerMoveFn, [128, 64, 64, 1]);
info(
"Test that moving the dragger with arrow keys emits color changed event."
);
// Focus on the spectrum dragger when spectrum is shown
s.dragger.focus();
is(
doc.activeElement.className,
"spectrum-color spectrum-box",
"Spectrum dragger has successfully received focus."
);
testChangingColorShouldEmitEventsHelper(s, sendDownKey, [125, 62, 62, 1]);
testChangingColorShouldEmitEventsHelper(s, sendLeftKey, [125, 63, 63, 1]);
testChangingColorShouldEmitEventsHelper(s, sendUpKey, [128, 64, 64, 1]);
testChangingColorShouldEmitEventsHelper(s, sendRightKey, [128, 63, 63, 1]);
info(
"Test that moving the hue slider with arrow keys emits color changed event."
);
// Tab twice to focus on hue slider
EventUtils.sendKey("Tab");
is(
doc.activeElement.className,
"devtools-button",
"Eyedropper has focus now."
);
EventUtils.sendKey("Tab");
is(
doc.activeElement.className,
"spectrum-hue-input",
"Hue slider has successfully received focus."
);
testChangingColorShouldEmitEventsHelper(s, sendRightKey, [128, 66, 63, 1]);
testChangingColorShouldEmitEventsHelper(s, sendLeftKey, [128, 63, 63, 1]);
info(
"Test that moving the hue slider with arrow keys emits color changed event."
);
// Tab to focus on alpha slider
EventUtils.sendKey("Tab");
is(
doc.activeElement.className,
"spectrum-alpha-input",
"Alpha slider has successfully received focus."
);
testChangingColorShouldEmitEventsHelper(s, sendLeftKey, [128, 63, 63, 0.99]);
testChangingColorShouldEmitEventsHelper(s, sendRightKey, [128, 63, 63, 1]);
s.destroy();
}
function setSpectrumProps(spectrum, props, updateUI = true) {
@ -188,15 +250,12 @@ function testSettingColorShoudUpdateTheUI(container) {
s.dragHelper.style.top,
s.dragHelper.style.left,
];
const alphaHelperOriginalPos = s.alphaSliderHelper.style.left;
let hueHelperOriginalPos = s.hueSliderHelper.style.left;
const alphaSliderOriginalVal = s.alphaSlider.value;
let hueSliderOriginalVal = s.hueSlider.value;
setSpectrumProps(s, { rgb: [50, 240, 234, 0.2] });
ok(
s.alphaSliderHelper.style.left != alphaHelperOriginalPos,
"Alpha helper has moved"
);
ok(s.alphaSlider.value != alphaSliderOriginalVal, "Alpha helper has moved");
ok(
s.dragHelper.style.top !== dragHelperOriginalPos[0],
"Drag helper has moved"
@ -205,22 +264,15 @@ function testSettingColorShoudUpdateTheUI(container) {
s.dragHelper.style.left !== dragHelperOriginalPos[1],
"Drag helper has moved"
);
ok(
s.hueSliderHelper.style.left !== hueHelperOriginalPos,
"Hue helper has moved"
);
ok(s.hueSlider.value !== hueSliderOriginalVal, "Hue helper has moved");
hueHelperOriginalPos = s.hueSliderHelper.style.left;
hueSliderOriginalVal = s.hueSlider.value;
setSpectrumProps(s, { rgb: ZERO_ALPHA_COLOR });
is(
s.alphaSliderHelper.style.left,
-(s.alphaSliderHelper.offsetWidth / 2) + "px",
"Alpha range UI has been updated again"
);
is(s.alphaSlider.value, 0, "Alpha range UI has been updated again");
ok(
hueHelperOriginalPos !== s.hueSliderHelper.style.left,
"Hue Helper slider should have move again"
hueSliderOriginalVal !== s.hueSlider.value,
"Hue slider should have move again"
);
s.destroy();

View File

@ -11,8 +11,15 @@ const L10N = new MultiLocalizationHelper(
"devtools/client/locales/en-US/accessibility.properties",
"devtools/client/locales/en-US/inspector.properties"
);
const ARROW_KEYS = ["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"];
const [ArrowUp, ArrowRight, ArrowDown, ArrowLeft] = ARROW_KEYS;
const XHTML_NS = "http://www.w3.org/1999/xhtml";
const COLOR_HEX_WHITE = "#ffffff";
const SLIDER = {
MIN: "0",
MAX: "128",
STEP: "1",
};
loader.lazyRequireGetter(this, "colorUtils", "devtools/shared/css/color", true);
loader.lazyRequireGetter(
@ -68,7 +75,7 @@ function Spectrum(parentEl, rgb) {
// eslint-disable-next-line no-unsanitized/property
this.element.innerHTML = `
<section class="spectrum-color-picker">
<div class="spectrum-color spectrum-box">
<div class="spectrum-color spectrum-box" tabindex="0">
<div class="spectrum-sat">
<div class="spectrum-val">
<div class="spectrum-dragger"></div>
@ -79,17 +86,9 @@ function Spectrum(parentEl, rgb) {
<section class="spectrum-controls">
<div class="spectrum-color-preview"></div>
<div class="spectrum-slider-container">
<div class="spectrum-hue spectrum-box">
<div class="spectrum-hue-inner">
<div class="spectrum-hue-handle spectrum-slider-control"></div>
</div>
</div>
<div class="spectrum-alpha spectrum-checker spectrum-box">
<div class="spectrum-alpha-inner">
<div class="spectrum-alpha-handle spectrum-slider-control"></div>
</div>
</div>
</div>
<div class="spectrum-hue spectrum-box"></div>
<div class="spectrum-alpha spectrum-checker spectrum-box"></div>
</div>
</section>
<section
class="spectrum-color-contrast accessibility-color-contrast"
@ -117,7 +116,11 @@ function Spectrum(parentEl, rgb) {
// Color spectrum dragger.
this.dragger = this.element.querySelector(".spectrum-color");
this.dragHelper = this.element.querySelector(".spectrum-dragger");
Spectrum.draggable(this.dragger, this.onDraggerMove.bind(this));
Spectrum.draggable(
this.dragger,
this.dragHelper,
this.onDraggerMove.bind(this)
);
// Here we define the components for the "controls" section of the color picker.
this.controls = this.element.querySelector(".spectrum-controls");
@ -130,17 +133,12 @@ function Spectrum(parentEl, rgb) {
eyedropper.style.pointerEvents = "auto";
this.controls.insertBefore(eyedropper, this.colorPreview);
// Hue slider
this.hueSlider = this.element.querySelector(".spectrum-hue");
this.hueSliderInner = this.element.querySelector(".spectrum-hue-inner");
this.hueSliderHelper = this.element.querySelector(".spectrum-hue-handle");
Spectrum.draggable(this.hueSliderInner, this.onHueSliderMove.bind(this));
// Alpha slider
this.alphaSlider = this.element.querySelector(".spectrum-alpha");
this.alphaSliderInner = this.element.querySelector(".spectrum-alpha-inner");
this.alphaSliderHelper = this.element.querySelector(".spectrum-alpha-handle");
Spectrum.draggable(this.alphaSliderInner, this.onAlphaSliderMove.bind(this));
// Hue slider and alpha slider
this.hueSlider = this.createSlider("hue", this.onHueSliderMove.bind(this));
this.alphaSlider = this.createSlider(
"alpha",
this.onAlphaSliderMove.bind(this)
);
// Color contrast
this.spectrumContrast = this.element.querySelector(
@ -243,10 +241,8 @@ Spectrum.rgbToHsv = function(r, g, b, a) {
return [h, s, v, a];
};
Spectrum.draggable = function(element, onmove, onstart, onstop) {
Spectrum.draggable = function(element, dragHelper, onmove) {
onmove = onmove || function() {};
onstart = onstart || function() {};
onstop = onstop || function() {};
const doc = element.ownerDocument;
let dragging = false;
@ -254,6 +250,12 @@ Spectrum.draggable = function(element, onmove, onstart, onstop) {
let maxHeight = 0;
let maxWidth = 0;
function setDraggerDimensionsAndOffset() {
maxHeight = element.offsetHeight;
maxWidth = element.offsetWidth;
offset = element.getBoundingClientRect();
}
function prevent(e) {
e.stopPropagation();
e.preventDefault();
@ -277,25 +279,20 @@ Spectrum.draggable = function(element, onmove, onstart, onstop) {
}
function start(e) {
const rightclick = e.which === 3;
const rightClick = e.which === 3;
if (!rightclick && !dragging) {
if (onstart.apply(element, arguments) !== false) {
dragging = true;
maxHeight = element.offsetHeight;
maxWidth = element.offsetWidth;
if (!rightClick && !dragging) {
dragging = true;
setDraggerDimensionsAndOffset();
offset = element.getBoundingClientRect();
move(e);
move(e);
doc.addEventListener("selectstart", prevent);
doc.addEventListener("dragstart", prevent);
doc.addEventListener("mousemove", move);
doc.addEventListener("mouseup", stop);
doc.addEventListener("selectstart", prevent);
doc.addEventListener("dragstart", prevent);
doc.addEventListener("mousemove", move);
doc.addEventListener("mouseup", stop);
prevent(e);
}
prevent(e);
}
}
@ -305,12 +302,37 @@ Spectrum.draggable = function(element, onmove, onstart, onstop) {
doc.removeEventListener("dragstart", prevent);
doc.removeEventListener("mousemove", move);
doc.removeEventListener("mouseup", stop);
onstop.apply(element, arguments);
}
dragging = false;
}
function onKeydown(e) {
const { key } = e;
if (!ARROW_KEYS.includes(key)) {
return;
}
setDraggerDimensionsAndOffset();
const { offsetHeight, offsetTop, offsetLeft } = dragHelper;
let dragX = offsetLeft + offsetHeight / 2;
let dragY = offsetTop + offsetHeight / 2;
if (key === ArrowLeft && dragX > 0) {
dragX -= 1;
} else if (key === ArrowRight && dragX < maxWidth) {
dragX += 1;
} else if (key === ArrowUp && dragY > 0) {
dragY -= 1;
} else if (key === ArrowDown && dragY < maxHeight) {
dragY += 1;
}
onmove.apply(element, [dragX, dragY]);
}
element.addEventListener("mousedown", start);
element.addEventListener("keydown", onKeydown);
};
/**
@ -396,12 +418,6 @@ Spectrum.prototype = {
this.dragHeight = this.dragger.offsetHeight;
this.dragHelperHeight = this.dragHelper.offsetHeight;
this.alphaSliderWidth = this.alphaSliderInner.offsetWidth;
this.alphaSliderHelperWidth = this.alphaSliderHelper.offsetWidth;
this.hueSliderWidth = this.hueSliderInner.offsetWidth;
this.hueSliderHelperWidth = this.hueSliderHelper.offsetWidth;
this.updateUI();
},
@ -409,8 +425,8 @@ Spectrum.prototype = {
e.stopPropagation();
},
onHueSliderMove: function(dragX, dragY) {
this.hsv[0] = dragX / this.hueSliderWidth;
onHueSliderMove: function() {
this.hsv[0] = this.hueSlider.value / this.hueSlider.max;
this.updateUI();
this.onChange();
},
@ -422,8 +438,8 @@ Spectrum.prototype = {
this.onChange();
},
onAlphaSliderMove: function(dragX, dragY) {
this.hsv[3] = dragX / this.alphaSliderWidth;
onAlphaSliderMove: function() {
this.hsv[3] = this.alphaSlider.value / this.alphaSlider.max;
this.updateUI();
this.onChange();
},
@ -432,6 +448,32 @@ Spectrum.prototype = {
this.emit("changed", this.rgb, this.rgbCssString);
},
/**
* Creates and initializes a slider element, attaches it to its parent container
* based on the slider type and returns it
*
* @param {String} sliderType
* The type of the slider (i.e. alpha or hue)
* @param {Function} onSliderMove
* The function to tie the slider to on input
* @return {DOMNode}
* Newly created slider
*/
createSlider: function(sliderType, onSliderMove) {
const container = this.element.querySelector(`.spectrum-${sliderType}`);
const slider = this.document.createElementNS(XHTML_NS, "input");
slider.className = `spectrum-${sliderType}-input`;
slider.type = "range";
slider.min = SLIDER.MIN;
slider.max = SLIDER.MAX;
slider.step = SLIDER.STEP;
slider.addEventListener("input", onSliderMove);
container.appendChild(slider);
return slider;
},
updateAlphaSliderBackground: function() {
const rgb = this.rgb;
@ -439,7 +481,7 @@ Spectrum.prototype = {
const rgbAlpha0 = "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ", 0)";
const alphaGradient =
"linear-gradient(to right, " + rgbAlpha0 + ", " + rgbNoAlpha + ")";
this.alphaSliderInner.style.background = alphaGradient;
this.alphaSlider.style.background = alphaGradient;
},
updateColorPreview: function() {
@ -488,13 +530,10 @@ Spectrum.prototype = {
this.dragHelper.style.left = dragX + "px";
// Placing the hue slider
const hueSliderX = h * this.hueSliderWidth - this.hueSliderHelperWidth / 2;
this.hueSliderHelper.style.left = hueSliderX + "px";
this.hueSlider.value = h * this.hueSlider.max;
// Placing the alpha slider
const alphaSliderX =
this.hsv[3] * this.alphaSliderWidth - this.alphaSliderHelperWidth / 2;
this.alphaSliderHelper.style.left = alphaSliderX + "px";
this.alphaSlider.value = this.hsv[3] * this.alphaSlider.max;
},
/* Calculates the contrast ratio for the currently selected
@ -560,11 +599,14 @@ Spectrum.prototype = {
destroy: function() {
this.element.removeEventListener("click", this.onElementClick);
this.hueSlider.removeEventListener("input", this.onHueSliderMove);
this.alphaSlider.removeEventListener("input", this.onAlphaSliderMove);
this.parentEl.removeChild(this.element);
this.dragger = null;
this.alphaSlider = this.alphaSliderInner = this.alphaSliderHelper = null;
this.dragger = this.dragHelper = null;
this.alphaSlider = null;
this.hueSlider = null;
this.colorPreview = null;
this.element = null;
this.parentEl = null;

View File

@ -29,14 +29,6 @@
background-position: 0 0, 6px 6px;
}
.spectrum-slider-control {
cursor: pointer;
box-shadow: 0 0 2px rgba(0,0,0,.6);
background: #fff;
border-radius: 50%;
opacity: .9;
}
.spectrum-box {
border: 1px solid rgba(0,0,0,0.2);
border-radius: 2px;
@ -135,18 +127,37 @@ http://www.briangrinstead.com/blog/keep-aspect-ratio-with-html-and-css */
height: 8px;
}
.spectrum-alpha-inner,
.spectrum-hue-inner {
height: 100%;
.spectrum-alpha-input,
.spectrum-hue-input {
width: 100%;
margin: 0;
position: absolute;
height: 8px;
border-radius: 2px;
}
.spectrum-alpha-handle,
.spectrum-hue-handle {
position: absolute;
top: -2px;
bottom: -2px;
/* Focus style already exists on input[type="range"]. Remove overlap */
.spectrum-hue-input:focus,
.spectrum-alpha-input:focus {
outline: none;
}
.spectrum-hue-input::-moz-range-thumb,
.spectrum-alpha-input::-moz-range-thumb {
cursor: pointer;
height: 12px;
width: 12px;
box-shadow: 0 0 2px rgba(0,0,0,.6);
background: #fff;
border-radius: 50%;
opacity: .9;
border: none;
}
.spectrum-hue-input::-moz-range-track {
border-radius: 2px;
height: 8px;
background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
}
.spectrum-sat {
@ -157,10 +168,6 @@ http://www.briangrinstead.com/blog/keep-aspect-ratio-with-html-and-css */
background-image: linear-gradient(to top, #000000, rgba(204, 154, 129, 0));
}
.spectrum-hue {
background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
}
.spectrum-dragger {
-moz-user-select: none;
position: absolute;
@ -174,14 +181,6 @@ http://www.briangrinstead.com/blog/keep-aspect-ratio-with-html-and-css */
box-shadow: 0 0 2px rgba(0,0,0,.6);
}
.spectrum-slider {
position: absolute;
top: 0;
height: 5px;
left: -3px;
right: -3px;
}
.spectrum-color-contrast {
padding-block-start: 8px;
padding-inline-start: 3px;
@ -236,7 +235,12 @@ http://www.briangrinstead.com/blog/keep-aspect-ratio-with-html-and-css */
margin-right: 1px;
}
.learn-more:hover {
.learn-more:hover, .learn-more:focus {
fill: var(--theme-icon-color);
cursor: pointer;
outline: none;
}
.learn-more::-moz-focus-inner {
border: none;
}

View File

@ -134,6 +134,11 @@ class SwatchBasedEditorTooltip {
}
hide() {
if (this.swatchActivatedWithKeyboard) {
this.activeSwatch.focus();
this.swatchActivatedWithKeyboard = null;
}
this.tooltip.hide();
}
@ -186,14 +191,22 @@ class SwatchBasedEditorTooltip {
}
_onSwatchClick(event) {
const swatch = this.swatches.get(event.target);
const { shiftKey, clientX, clientY, target } = event;
if (event.shiftKey) {
// If mouse coordinates are 0, the event listener could have been triggered
// by a keybaord
this.swatchActivatedWithKeyboard =
event.key && clientX === 0 && clientY === 0;
if (shiftKey) {
event.stopPropagation();
return;
}
const swatch = this.swatches.get(target);
if (swatch) {
this.activeSwatch = event.target;
this.activeSwatch = target;
this.show();
swatch.callbacks.onShow();
event.stopPropagation();

View File

@ -16,9 +16,21 @@ const {
A11Y_CONTRAST_LEARN_MORE_LINK,
} = require("devtools/client/accessibility/constants");
loader.lazyRequireGetter(
this,
"wrapMoveFocus",
"devtools/client/shared/focus",
true
);
loader.lazyRequireGetter(
this,
"getFocusableElements",
"devtools/client/shared/focus",
true
);
const TELEMETRY_PICKER_EYEDROPPER_OPEN_COUNT =
"DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT";
const XHTML_NS = "http://www.w3.org/1999/xhtml";
/**
@ -49,7 +61,10 @@ class SwatchColorPickerTooltip extends SwatchBasedEditorTooltip {
this._onSpectrumColorChange = this._onSpectrumColorChange.bind(this);
this._openEyeDropper = this._openEyeDropper.bind(this);
this._openDocLink = this._openDocLink.bind(this);
this._onTooltipKeydown = this._onTooltipKeydown.bind(this);
this.cssColor4 = supportsCssColor4ColorFunction();
this.tooltip.container.addEventListener("keydown", this._onTooltipKeydown);
}
/**
@ -143,6 +158,7 @@ class SwatchColorPickerTooltip extends SwatchBasedEditorTooltip {
);
if (learnMoreButton) {
learnMoreButton.addEventListener("click", this._openDocLink);
learnMoreButton.addEventListener("keydown", e => e.stopPropagation());
}
// After spectrum properties are set, update the tooltip content size.
@ -150,9 +166,39 @@ class SwatchColorPickerTooltip extends SwatchBasedEditorTooltip {
// and tooltip size needs to be updated to account for it.
this.tooltip.updateContainerBounds(super.tooltipAnchor);
// Add focus to the first focusable element in the tooltip and attach keydown
// event listener to tooltip
this.focusableElements[0].focus();
this.tooltip.container.addEventListener(
"keydown",
this._onTooltipKeydown,
true
);
this.emit("ready");
}
_onTooltipKeydown(event) {
const { target, key, shiftKey } = event;
if (key !== "Tab") {
return;
}
const focusMoved = !!wrapMoveFocus(
this.focusableElements,
target,
shiftKey
);
if (focusMoved) {
// Focus was moved to the begining/end of the tooltip, so we need to prevent the
// default focus change that would happen here.
event.preventDefault();
}
event.stopPropagation();
}
_onSpectrumColorChange(rgba, cssColor) {
this._selectColor(cssColor);
}
@ -183,6 +229,10 @@ class SwatchColorPickerTooltip extends SwatchBasedEditorTooltip {
}
super.onTooltipHidden();
this.tooltip.container.removeEventListener(
"keydown",
this._onTooltipKeydown
);
}
_openEyeDropper() {
@ -248,6 +298,12 @@ class SwatchColorPickerTooltip extends SwatchBasedEditorTooltip {
return this.tooltip.isVisible() || this.eyedropperOpen;
}
get focusableElements() {
return getFocusableElements(this.tooltip.container).filter(
el => !!el.offsetParent
);
}
destroy() {
super.destroy();
this.inspector = null;

View File

@ -545,6 +545,11 @@
z-index: -1;
}
.ruleview-swatch.ruleview-colorswatch {
border: none;
padding: 0;
}
.ruleview-bezierswatch {
background-image: url("chrome://devtools/skin/images/cubic-bezier-swatch.svg");
}

View File

@ -366,7 +366,7 @@
"devtools/client/inspector/rules/test/browser_rules_class_panel_state_preserved.js": 3655,
"devtools/client/inspector/rules/test/browser_rules_colorUnit.js": 8353,
"devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js": 3600,
"devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click.js": 3714,
"devtools/client/inspector/rules/test/browser_rules_colorpicker-appears-on-swatch-click-or-keyboard-activation.js": 3714,
"devtools/client/inspector/rules/test/browser_rules_colorpicker-hides-on-tooltip.js": 3620,
"devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js": 4665,
"devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js": 3580,