((exports) => {

	// @TODO:
	// - Persist custom colors list across reloads? It's not very persistent in real Windows...
	// - maybe use https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Grid_Role
	// - Question mark button in titlebar that lets you click on parts of UI to ask about them; also context menu "What's this?"
	// - For mobile layout, maybe add a way to get back (<<) without adding (potentially overwriting) a custom color
	// - Speech recognition
	//   - Lum as Luminosity, Luminance, Lightness, maybe even Brightness
	//   - Sat as Saturation
	//   - Add / Add Color / Add Custom Color for Add To Custom Colors or if not available then Define Custom Colors >>
	//   - Set green to 50 etc.

	// In Windows, the Hue goes from 0 to 239 (240 being equivalent to 0), and Sat and Lum go from 0 to 240
	// I think people are more familiar with degrees and percentages, so I don't think I'll be implementing that.

	// Development workflow:
	// - In the console, set localStorage.dev_edit_colors = "true";
	// - Reload the page
	// - Load a screenshot of the Edit Colors window into the editor
	// - Position it finely using the arrow keys on a selection
	// - For measuring positions, look at the Windows source code OR:
	//   - close the window,
	//   - point on the canvas, mark down the coordinates shown in status bar,
	//   - point on the canvas at the origin
	//     - the top left of the inside of the window, or
	//     - the top left of (what corresponds to) the nearest parent position:fixed/absolute/relative
	//   - subtract the origin from the target

	let $edit_colors_window;

	let dev_edit_colors = false;
	try {
		dev_edit_colors = localStorage.dev_edit_colors === "true";
		// eslint-disable-next-line no-empty
	} catch (error) { }
	if (dev_edit_colors) {
		$(() => {
			show_edit_colors_window();
			$(".define-custom-colors-button").click();
			$edit_colors_window.css({
				left: 80,
				top: 50,
				opacity: 0.5,
			});
		});
	}

	// Paint-specific handling of color picking
	// Note: It always updates a cell in the palette and one of the color selections.
	// When the dialog is opened, it always starts* with one of the color selections,
	// which lets you use the color picker and then add a custom color based on that.
	// *It may not show the color in the grid, but it will in the custom colors area.
	function show_edit_colors_window($swatch_to_edit, color_selection_slot_to_edit) {
		// console.log($swatch_to_edit, $colorbox.data("$last_fg_color_button"));
		$swatch_to_edit = $swatch_to_edit || $colorbox.data("$last_fg_color_button");
		color_selection_slot_to_edit = color_selection_slot_to_edit || "foreground";

		const $palette = $swatch_to_edit.closest(".palette, .color-box");
		const swatch_index = $palette.find(".swatch").toArray().indexOf($swatch_to_edit[0]);
		const initial_color = selected_colors[color_selection_slot_to_edit];
		choose_color(initial_color, (color) => {
			// The palette may have changed or rerendered due to switching themes,
			// toggling vertical color box mode, or monochrome document mode.
			$swatch_to_edit = $($palette.find(".swatch")[swatch_index]);
			if (!$swatch_to_edit.length) {
				show_error_message("Swatch no longer exists.");
				return;
			}

			if (monochrome && (swatch_index === 0 || swatch_index === 14)) {
				// when editing first color in first or second row (the solid base colors),
				// update whole monochrome patterns palette and image
				let old_rgba = get_rgba_from_color(palette[swatch_index]);
				const new_rgba = get_rgba_from_color(color);
				const other_rgba = get_rgba_from_color(palette[14 - swatch_index]);
				const main_monochrome_info = detect_monochrome(main_ctx);
				const selection_monochrome_info = (selection && selection.canvas) ? detect_monochrome(selection.canvas.ctx) : main_monochrome_info;
				const selection_matches_main_canvas_colors =
					selection_monochrome_info.isMonochrome &&
					selection_monochrome_info.presentNonTransparentRGBAs.every((rgba) =>
						main_monochrome_info.presentNonTransparentRGBAs.map(rgba => rgba.toString()).includes(rgba.toString())
					);
				if (
					main_monochrome_info.isMonochrome &&
					selection_monochrome_info.isMonochrome &&
					selection_matches_main_canvas_colors
				) {
					const recolor = (ctx, present_rgbas) => {
						// HTML5 Canvas API is unreliable for exact colors.
						// 1. The specifications specify unpremultiplied alpha, but in practice browsers use premultiplied alpha for performance.
						// 2. Some browsers implement protections against fingerprinting that return slightly random data
						// 3. There may be color profiles that affect returned pixel values vs color table values when loading images.
						//    (BMPs are supposed to be able to embed ICC profiles although I doubt it's prevalent.
						//    Some global system color profile might apply however, I don't know how all that works.)
						if (
							present_rgbas.length === 2 &&
							present_rgbas.every((present_rgba) => `${present_rgba}` !== `${old_rgba}`)
						) {
							// Find the nearer color in the image data to replace.
							const distances = present_rgbas.map((rgba) =>
								Math.abs(rgba[0] - old_rgba[0]) +
								Math.abs(rgba[1] - old_rgba[1]) +
								Math.abs(rgba[2] - old_rgba[2]) +
								Math.abs(rgba[3] - old_rgba[3])
							);
							if (distances[0] < distances[1]) {
								old_rgba = present_rgbas[0];
							} else {
								old_rgba = present_rgbas[1];
							}
						}
						const image_data = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
						replace_color_globally(image_data, old_rgba[0], old_rgba[1], old_rgba[2], old_rgba[3], new_rgba[0], new_rgba[1], new_rgba[2], new_rgba[3]);
						ctx.putImageData(image_data, 0, 0);
					};
					undoable({
						name: "Recolor",
						icon: get_help_folder_icon("p_color.png"),
					}, () => {
						recolor(main_ctx, main_monochrome_info.presentNonTransparentRGBAs);
						if (selection && selection.canvas) {
							recolor(selection.canvas.ctx, selection_monochrome_info.presentNonTransparentRGBAs);
							// I feel like this shouldn't be necessary, if I'm not changing the size, but it makes it work:
							selection.replace_source_canvas(selection.canvas);
						}
					});
				}
				if (swatch_index) {
					palette = make_monochrome_palette(other_rgba, new_rgba);
				} else {
					palette = make_monochrome_palette(new_rgba, other_rgba);
				}
				$colorbox.rebuild_palette();
				selected_colors.foreground = palette[0];
				selected_colors.background = palette[14]; // first in second row
				selected_colors.ternary = "";
				$G.triggerHandler("option-changed");
			} else {
				palette[swatch_index] = color;
				update_$swatch($swatch_to_edit, color);
				selected_colors[color_selection_slot_to_edit] = color;
				$G.triggerHandler("option-changed");
				window.console && console.log(`Updated palette: ${palette.map(() => `%c█`).join("")}`, ...palette.map((color) => `color: ${color};`));
			}
		});
	}

	// Repurposable color picker modeled after the Windows system color picker
	function choose_color(initial_color, callback) {
		if ($edit_colors_window) {
			$edit_colors_window.close();
		}
		const $w = new $DialogWindow(localize("Edit Colors"));
		$w.addClass("edit-colors-window");
		$edit_colors_window = $w;

		let hue_degrees = 0;
		let sat_percent = 50;
		let lum_percent = 50;

		let custom_colors_index = 0;

		const get_current_color = () => `hsl(${hue_degrees}deg, ${sat_percent}%, ${lum_percent}%)`;
		const set_color_from_rgb = (r, g, b) => {
			const [h, s, l] = rgb_to_hsl(r, g, b);
			hue_degrees = h * 360;
			sat_percent = s * 100;
			lum_percent = l * 100;
		};
		const set_color = (color) => {
			const [r, g, b] = get_rgba_from_color(color);
			set_color_from_rgb(r, g, b);
		};
		const select = ($swatch) => {
			$w.$content.find(".swatch").removeClass("selected");
			$swatch.addClass("selected");
			set_color($swatch[0].dataset.color);
			if ($swatch.closest("#custom-colors")) {
				custom_colors_index = Math.max(0, custom_colors_swatches_list_order.indexOf(
					$custom_colors_grid.find(".swatch.selected")[0]
				));
			}
			update_inputs("hslrgb");
		};

		const make_color_grid = (colors, id) => {
			const $color_grid = $(`<div class="color-grid" tabindex="0">`).attr({ id });
			for (const color of colors) {
				const $swatch = $Swatch(color);
				$swatch.appendTo($color_grid).addClass("inset-deep");
				$swatch.attr("tabindex", -1); // can be focused by clicking or calling focus() but not by tabbing
			}
			let $local_last_focus = $color_grid.find(".swatch:first-child");
			const num_colors_per_row = 8;
			const navigate = (relative_index) => {
				const $focused = $color_grid.find(".swatch:focus");
				if (!$focused.length) { return; }
				const $swatches = $color_grid.find(".swatch");
				const from_index = $swatches.toArray().indexOf($focused[0]);
				if (relative_index === -1 && (from_index % num_colors_per_row) === 0) { return; }
				if (relative_index === +1 && (from_index % num_colors_per_row) === num_colors_per_row - 1) { return; }
				const to_index = from_index + relative_index;
				const $to_focus = $($swatches.toArray()[to_index]);
				// console.log({from_index, to_index, $focused, $to_focus});
				if (!$to_focus.length) { return; }
				$to_focus.focus();
			};
			$color_grid.on("keydown", (event) => {
				// console.log(event.code);
				if (event.code === "ArrowRight") { navigate(+1); }
				if (event.code === "ArrowLeft") { navigate(-1); }
				if (event.code === "ArrowDown") { navigate(+num_colors_per_row); }
				if (event.code === "ArrowUp") { navigate(-num_colors_per_row); }
				if (event.code === "Home") { $color_grid.find(".swatch:first-child").focus(); }
				if (event.code === "End") { $color_grid.find(".swatch:last-child").focus(); }
				if (event.code === "Space" || event.code === "Enter") {
					select($color_grid.find(".swatch:focus"));
					draw();
				}
			});
			$color_grid.on("pointerdown", (event) => {
				const $swatch = $(event.target).closest(".swatch");
				if ($swatch.length) {
					select($swatch);
					draw();
				}
			});
			$color_grid.on("dragstart", (event) => {
				event.preventDefault();
			});
			$color_grid.on("focusin", (event) => {
				if (event.target.closest(".swatch")) {
					$local_last_focus = $(event.target.closest(".swatch"));
				} else {
					if (!$local_last_focus.is(":focus")) { // prevent infinite recursion
						$local_last_focus.focus();
					}
				}
				// allow shift+tabbing out of the control
				// otherwise it keeps setting focus back to the color cell,
				// since the parent grid is previous in the tab order
				$color_grid.attr("tabindex", -1);
			});
			$color_grid.on("focusout", (event) => {
				$color_grid.attr("tabindex", 0);
			});
			return $color_grid;
		};
		const $left_right_split = $(`<div class="left-right-split">`).appendTo($w.$main);
		const $left = $(`<div class="left-side">`).appendTo($left_right_split);
		const $right = $(`<div class="right-side">`).appendTo($left_right_split).hide();
		$left.append(`<label for="basic-colors">${display_hotkey("&Basic colors:")}</label>`);
		const $basic_colors_grid = make_color_grid(basic_colors, "basic-colors").appendTo($left);
		$left.append(`<label for="custom-colors">${display_hotkey("&Custom colors:")}</label>`);
		const custom_colors_dom_order = []; // (wanting) horizontal top to bottom
		for (let list_index = 0; list_index < custom_colors.length; list_index++) {
			const row = list_index % 2;
			const column = Math.floor(list_index / 2);
			const dom_index = row * 8 + column;
			custom_colors_dom_order[dom_index] = custom_colors[list_index];
		}
		const $custom_colors_grid = make_color_grid(custom_colors_dom_order, "custom-colors").appendTo($left);
		const custom_colors_swatches_dom_order = $custom_colors_grid.find(".swatch").toArray(); // horizontal top to bottom
		const custom_colors_swatches_list_order = []; // (wanting) vertical left to right
		for (let dom_index = 0; dom_index < custom_colors_swatches_dom_order.length; dom_index++) {
			const row = Math.floor(dom_index / 8);
			const column = dom_index % 8;
			const list_index = column * 2 + row;
			custom_colors_swatches_list_order[list_index] = custom_colors_swatches_dom_order[dom_index];
			// custom_colors_swatches_list_order[list_index].textContent = list_index; // visualization
		}

		const $define_custom_colors_button = $(`<button class="define-custom-colors-button" type="button">`)
			.html(display_hotkey("&Define Custom Colors >>"))
			.appendTo($left)
			.on("click", (e) => {
				// prevent the form from submitting
				// @TODO: instead, prevent the form's submit event in $Window.js in os-gui (or don't have a form? idk)
				e.preventDefault();

				$right.show();
				$w.addClass("defining-custom-colors"); // for mobile layout
				$define_custom_colors_button.attr("disabled", "disabled");
				// assuming small viewport implies mobile implies an onscreen keyboard,
				// and that you probably don't want to use the keyboard to choose colors
				if ($w.width() >= 300) {
					inputs_by_component_letter.h.focus();
				}
				maybe_reenable_button_for_mobile_navigation();
			});

		// for mobile layout, re-enable button because it's a navigation button in that case, rather than one-time expand action
		const maybe_reenable_button_for_mobile_navigation = () => {
			// if ($right.is(":hidden")) {
			if ($w.width() < 300 || document.body.classList.contains("eye-gaze-mode")) {
				$define_custom_colors_button.removeAttr("disabled");
			}
		};
		$(window).on("resize", maybe_reenable_button_for_mobile_navigation);

		const $color_solid_label = $(`<label for="color-solid-canvas">${display_hotkey("Color|S&olid")}</label>`);
		$color_solid_label.css({
			position: "absolute",
			left: 10,
			top: 244,
		});

		const rainbow_canvas = make_canvas(175, 187);
		const luminosity_canvas = make_canvas(10, 187);
		const result_canvas = make_canvas(58, 40);
		const lum_arrow_canvas = make_canvas(5, 9);

		$(result_canvas).css({
			position: "absolute",
			left: 10,
			top: 198,
		});

		let mouse_down_on_rainbow_canvas = false;
		let crosshair_shown_on_rainbow_canvas = false;
		const draw = () => {
			if (!mouse_down_on_rainbow_canvas || crosshair_shown_on_rainbow_canvas) {
				// rainbow
				for (let y = 0; y < rainbow_canvas.height; y += 6) {
					for (let x = -1; x < rainbow_canvas.width; x += 3) {
						rainbow_canvas.ctx.fillStyle = `hsl(${x / rainbow_canvas.width * 360}deg, ${(1 - y / rainbow_canvas.height) * 100}%, 50%)`;
						rainbow_canvas.ctx.fillRect(x, y, 3, 6);
					}
				}
				// crosshair
				if (!mouse_down_on_rainbow_canvas) {
					const x = ~~(hue_degrees / 360 * rainbow_canvas.width);
					const y = ~~((1 - sat_percent / 100) * rainbow_canvas.height);
					rainbow_canvas.ctx.fillStyle = "black";
					rainbow_canvas.ctx.fillRect(x - 1, y - 9, 3, 5);
					rainbow_canvas.ctx.fillRect(x - 1, y + 5, 3, 5);
					rainbow_canvas.ctx.fillRect(x - 9, y - 1, 5, 3);
					rainbow_canvas.ctx.fillRect(x + 5, y - 1, 5, 3);
				}
				crosshair_shown_on_rainbow_canvas = !mouse_down_on_rainbow_canvas;
			}

			for (let y = -2; y < luminosity_canvas.height; y += 6) {
				luminosity_canvas.ctx.fillStyle = `hsl(${hue_degrees}deg, ${sat_percent}%, ${(1 - y / luminosity_canvas.height) * 100}%)`;
				luminosity_canvas.ctx.fillRect(0, y, luminosity_canvas.width, 6);
			}

			lum_arrow_canvas.ctx.fillStyle = getComputedStyle($w.$content[0]).getPropertyValue("--ButtonText");
			for (let x = 0; x < lum_arrow_canvas.width; x++) {
				lum_arrow_canvas.ctx.fillRect(x, lum_arrow_canvas.width - x - 1, 1, 1 + x * 2);
			}
			lum_arrow_canvas.style.position = "absolute";
			lum_arrow_canvas.style.left = "215px";
			lum_arrow_canvas.style.top = `${3 + ~~((1 - lum_percent / 100) * luminosity_canvas.height)}px`;

			result_canvas.ctx.fillStyle = get_current_color();
			result_canvas.ctx.fillRect(0, 0, result_canvas.width, result_canvas.height);
		};
		draw();
		$(rainbow_canvas).addClass("rainbow-canvas inset-shallow");
		$(luminosity_canvas).addClass("luminosity-canvas inset-shallow");
		$(result_canvas).addClass("result-color-canvas inset-shallow").attr("id", "color-solid-canvas");

		const select_hue_sat = (event) => {
			hue_degrees = Math.min(1, Math.max(0, event.offsetX / rainbow_canvas.width)) * 360;
			sat_percent = Math.min(1, Math.max(0, (1 - event.offsetY / rainbow_canvas.height))) * 100;
			update_inputs("hsrgb");
			draw();
			event.preventDefault();
		};
		$(rainbow_canvas).on("pointerdown", (event) => {
			mouse_down_on_rainbow_canvas = true;
			select_hue_sat(event);

			$(rainbow_canvas).on("pointermove", select_hue_sat);
			if (event.pointerId !== 1234567890) { // for Eye Gaze Mode simulated clicks
				rainbow_canvas.setPointerCapture(event.pointerId);
			}
		});
		$G.on("pointerup pointercancel", (event) => {
			$(rainbow_canvas).off("pointermove", select_hue_sat);
			// rainbow_canvas.releasePointerCapture(event.pointerId);
			mouse_down_on_rainbow_canvas = false;
			draw();
		});

		const select_lum = (event) => {
			lum_percent = Math.min(1, Math.max(0, (1 - event.offsetY / luminosity_canvas.height))) * 100;
			update_inputs("lrgb");
			draw();
			event.preventDefault();
		};
		$(luminosity_canvas).on("pointerdown", (event) => {
			select_lum(event);

			$(luminosity_canvas).on("pointermove", select_lum);
			if (event.pointerId !== 1234567890) { // for Eye Gaze Mode simulated clicks
				luminosity_canvas.setPointerCapture(event.pointerId);
			}
		});
		$G.on("pointerup pointercancel", (event) => {
			$(luminosity_canvas).off("pointermove", select_lum);
			// luminosity_canvas.releasePointerCapture(event.pointerId);
		});

		const inputs_by_component_letter = {};

		["hsl", "rgb"].forEach((color_model, color_model_index) => {
			[...color_model].forEach((component_letter, component_index) => {
				const text_with_hotkey = {
					h: "Hu&e:",
					s: "&Sat:",
					l: "&Lum:",
					r: "&Red:",
					g: "&Green:",
					b: "Bl&ue:",
				}[component_letter];
				const input = document.createElement("input");
				// not doing type="number" because the inputs have no up/down buttons and they have special behavior with validation
				input.type = "text";
				input.classList.add("inset-deep");
				input.dataset.componentLetter = component_letter;
				input.dataset.min = 0;
				input.dataset.max = {
					h: 360,
					s: 100,
					l: 100,
					r: 255,
					g: 255,
					b: 255,
				}[component_letter];
				const label = document.createElement("label");
				label.innerHTML = display_hotkey(text_with_hotkey);
				const input_y_spacing = 22;
				$(label).css({
					position: "absolute",
					left: 63 + color_model_index * 80,
					top: 202 + component_index * input_y_spacing,
					textAlign: "right",
					display: "inline-block",
					width: 40,
					height: 20,
					lineHeight: "20px",
				});
				$(input).css({
					position: "absolute",
					left: 106 + color_model_index * 80,
					top: 202 + component_index * input_y_spacing + (component_index > 1), // spacing of rows is uneven by a pixel
					width: 21,
					height: 14,
				});
				$right.append(label, input);

				inputs_by_component_letter[component_letter] = input;
			});
		});

		// listening for input events on input elements using event delegation (looks a little weird)
		$right.on("input", "input", (event) => {
			const input = event.target;
			const component_letter = input.dataset.componentLetter;
			if (component_letter) {
				// In Windows, it actually only updates if the numerical value changes, not just the text.
				// That is, you can add leading zeros, and they'll stay, then add them in the other color model
				// and it won't remove the ones in the fields of the first color model.
				// This is not important, so I don't know if I'll do that.

				if (input.value.match(/^\d+$/)) {
					let n = Number(input.value);
					if (n < input.dataset.min) {
						n = input.dataset.min;
						input.value = n;
					} else if (n > input.dataset.max) {
						n = input.dataset.max;
						input.value = n;
					}
					if ("hsl".indexOf(component_letter) > -1) {
						switch (component_letter) {
							case "h":
								hue_degrees = n;
								break;
							case "s":
								sat_percent = n;
								break;
							case "l":
								lum_percent = n;
								break;
						}
						update_inputs("rgb");
					} else {
						let [r, g, b] = get_rgba_from_color(get_current_color());
						const rgb = { r, g, b };
						rgb[component_letter] = n;
						set_color_from_rgb(rgb.r, rgb.g, rgb.b);
						update_inputs("hsl");
					}
					draw();
				} else if (input.value.length) {
					update_inputs(component_letter);
					input.select();
				}
			}
		});
		$right.on("focusout", "input", (event) => {
			const input = event.target;
			const component_letter = input.dataset.componentLetter;
			if (component_letter) {
				// Handle empty input when focus moves away
				if (!input.value.match(/^\d+$/)) {
					update_inputs(component_letter);
					input.select();
				}
			}
		});

		$w.on("keydown", (event) => {
			// For some reason Enter isn't working to submit the form. (Am I preventing it somewhere?)
			// It's understandable that it wouldn't work for my custom grid controls,
			// but it's not submitting even when regular inputs are focused.
			if (event.code === "Enter" && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
				// There are no controls in this dialog that need to handle Enter like a multi-line textarea,
				// other than buttons, which should trigger the specific button,
				// and color cells, which should select the color and submit the dialog.
				// The color should be already selected, by the more specific event handler, as the event bubbles up.
				if (!event.target.closest("button")) {
					callback(get_current_color());
					$w.close();
					event.preventDefault();
					event.stopPropagation();
					return;
				}
			}

			if (event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
				switch (event.key) {
					case "o":
						set_color(get_current_color());
						update_inputs("hslrgb");
						draw();
						break;
					case "b":
						$basic_colors_grid.find(".swatch.selected, .swatch").focus();
						break;
					case "c":
						$basic_colors_grid.find(".swatch.selected, .swatch").focus();
						break;
					case "e":
						inputs_by_component_letter.h.focus();
						break;
					case "s":
						inputs_by_component_letter.s.focus();
						break;
					case "l":
						inputs_by_component_letter.l.focus();
						break;
					case "r":
						inputs_by_component_letter.r.focus();
						break;
					case "g":
						inputs_by_component_letter.g.focus();
						break;
					case "u":
						inputs_by_component_letter.b.focus();
						break;
					case "a":
						if ($add_to_custom_colors_button.is(":visible")) {
							$add_to_custom_colors_button.click();
						}
						break;
					case "d":
						$define_custom_colors_button.click();
						break;
					default:
						return; // don't prevent default by default
				}
			} else {
				return; // don't prevent default by default
			}
			event.preventDefault();
			event.stopPropagation();
		});

		const update_inputs = (components) => {
			for (const component_letter of components) {
				const input = inputs_by_component_letter[component_letter];
				const [r, g, b] = get_rgba_from_color(get_current_color());
				input.value = Math.floor({
					h: hue_degrees,
					s: sat_percent,
					l: lum_percent,
					r,
					g,
					b,
				}[component_letter]);
			}
		};

		$right.append(rainbow_canvas, luminosity_canvas, result_canvas, $color_solid_label, lum_arrow_canvas);

		const $add_to_custom_colors_button = $(`<button class="add-to-custom-colors-button" type="button">`)
			.html(display_hotkey("&Add To Custom Colors"))
			.appendTo($right)
			.on("click", (event) => {
				// prevent the form from submitting
				// @TODO: instead, prevent the form's submit event in $Window.js in os-gui (or don't have a form? idk)
				event.preventDefault();

				const color = get_current_color();
				custom_colors[custom_colors_index] = color;
				// console.log(custom_colors_swatches_reordered, custom_colors_index, custom_colors_swatches_reordered[custom_colors_index]));
				update_$swatch($(custom_colors_swatches_list_order[custom_colors_index]), color);
				custom_colors_index = (custom_colors_index + 1) % custom_colors.length;

				$w.removeClass("defining-custom-colors"); // for mobile layout
			});

		$w.$Button(localize("OK"), () => {
			callback(get_current_color());
			$w.close();
		}, { type: "submit" });
		$w.$Button(localize("Cancel"), () => {
			$w.close();
		});

		$left.append($w.$buttons);

		// Initially select the first color cell that matches the color to edit, if any
		// (first in the basic colors, then in the custom colors otherwise.
		// This works implicitly, since basic colors come before custom colors in the DOM.)
		for (const swatch_el of $left.find(".swatch").toArray()) {
			if (get_rgba_from_color(swatch_el.dataset.color).join(",") === get_rgba_from_color(initial_color).join(",")) {
				select($(swatch_el));
				swatch_el.focus();
				break;
			}
		}
		custom_colors_index = Math.max(0, custom_colors_swatches_list_order.indexOf(
			$custom_colors_grid.find(".swatch.selected")[0]
		));
		// If no color cell matches the color to edit,
		// focus the first color cell, without changing the selected color value as displayed if you expand the dialog.
		// This supports workflows:
		// 1. Make a custom color, without saving it to the custom colors list, hit OK, then edit this new color.
		// 2. Use the eye dropper tool to select a color in an image, then edit it or see the RGB/HSL values.
		// (Also test adding to custom colors, without editing, a color not already in the custom colors list.
		// I swear it added the wrong color once...)
		if ($w.find(".swatch:focus").length === 0) {
			$w.find(".swatch").first().focus();
		}

		set_color(initial_color);
		update_inputs("hslrgb");

		$w.center();
	}

	exports.show_edit_colors_window = show_edit_colors_window;

})(window);