mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 03:15:11 +00:00
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:
parent
8cfcbe667f
commit
7bd7b72040
@ -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]
|
||||
|
@ -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");
|
@ -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."
|
||||
);
|
||||
}
|
@ -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,
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user