i18n_proofreading 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/i18n_proofreading/version.rb +1 -1
- data/lib/i18n_proofreading/widget.js +52 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f5a99dfd18ee5650a22a7511b6dc7cd061c4be32dae3d2ab7442174ce226b105
|
|
4
|
+
data.tar.gz: dbc832b5abd4f24d57927dec1e5cd5a593498ca4a818609488c0b61355ecba35
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c1aa74a2f8d4286a83b1b4b94b1e149999c1769f9512b3990e7e389e637e9c96db4645b041dfd506abb8948b9d9011d59995145ed8fab6c0fffe2901cf8ad50
|
|
7
|
+
data.tar.gz: d5e1166331606efb1b6a957c2b004360ad86c9fdae1d3846d2907f625ab52607e96bce3e73c5106910acdeeae4d2733f30e9ed6e43a335e6ecbdb6ada7001016
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.4]
|
|
6
|
+
|
|
7
|
+
- **Accessibility parity for the suggestion popover.** The panel is now a real
|
|
8
|
+
dialog (`role="dialog"`, `aria-modal="true"`, named by its title via
|
|
9
|
+
`aria-labelledby`); Tab/Shift+Tab are trapped inside it while it is open and
|
|
10
|
+
closing it returns focus to the element that opened it. Validation and save
|
|
11
|
+
errors are announced to screen readers (`role="alert"`), and the previously
|
|
12
|
+
hardcoded English network-failure message now goes through the localized
|
|
13
|
+
`errorSave` label like the rest. The page behind the open dialog is
|
|
14
|
+
scroll-locked (`overflow: hidden` on `<html>`, restored on close) — and if a
|
|
15
|
+
Turbo body swap removes the overlay without `close()`, the next render
|
|
16
|
+
releases the lock so the new page never arrives frozen. The floating pill is
|
|
17
|
+
untouched.
|
|
18
|
+
|
|
5
19
|
## [0.9.3]
|
|
6
20
|
|
|
7
21
|
- Fix the widget going dead after a Turbo Drive visit under a nonce-based
|
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
var errorNode = null;
|
|
35
35
|
var saveButton = null;
|
|
36
36
|
var priorNode = null;
|
|
37
|
+
var openerNode = null;
|
|
38
|
+
var savedOverflow = null; // pre-lock inline overflow of <html>; null = not locked
|
|
37
39
|
|
|
38
40
|
function ready(fn) {
|
|
39
41
|
if (document.readyState === "loading") {
|
|
@@ -73,6 +75,12 @@
|
|
|
73
75
|
// Re-read on each visit: the injected config is data (not an executed
|
|
74
76
|
// script), so it reflects the page Turbo just rendered.
|
|
75
77
|
config = readConfig() || config;
|
|
78
|
+
// A Turbo body swap can drop the overlay without close(); documentElement
|
|
79
|
+
// survives the swap, so release the scroll lock or the new page is frozen.
|
|
80
|
+
if (overlay && !document.body.contains(overlay)) {
|
|
81
|
+
overlay = null;
|
|
82
|
+
unlockScroll();
|
|
83
|
+
}
|
|
76
84
|
injectStyles();
|
|
77
85
|
if (config.showPill !== false) buildPill();
|
|
78
86
|
document.documentElement.classList.toggle("i18np-active", !!config.active);
|
|
@@ -144,11 +152,30 @@
|
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
function handleKeydown(event) {
|
|
155
|
+
if (event.key === "Tab" && overlay) {
|
|
156
|
+
trapTab(event);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
147
159
|
if (event.key !== "Escape") return;
|
|
148
160
|
if (overlay) close();
|
|
149
161
|
else if (config.active) toggle();
|
|
150
162
|
}
|
|
151
163
|
|
|
164
|
+
// Keep Tab/Shift+Tab cycling inside the open dialog.
|
|
165
|
+
function trapTab(event) {
|
|
166
|
+
var focusable = overlay.querySelectorAll("button, [href], input, textarea, select");
|
|
167
|
+
if (!focusable.length) return;
|
|
168
|
+
var first = focusable[0];
|
|
169
|
+
var last = focusable[focusable.length - 1];
|
|
170
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
last.focus();
|
|
173
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
first.focus();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
152
179
|
// --- pill -----------------------------------------------------------------
|
|
153
180
|
|
|
154
181
|
function buildPill() {
|
|
@@ -178,12 +205,17 @@
|
|
|
178
205
|
function open(key, currentValue) {
|
|
179
206
|
close();
|
|
180
207
|
|
|
208
|
+
openerNode = document.activeElement;
|
|
209
|
+
|
|
181
210
|
overlay = el("div", "i18np-overlay");
|
|
182
211
|
overlay.addEventListener("click", function (event) {
|
|
183
212
|
if (event.target === overlay) close();
|
|
184
213
|
});
|
|
185
214
|
|
|
186
215
|
var panel = el("div", "i18np-panel");
|
|
216
|
+
panel.setAttribute("role", "dialog");
|
|
217
|
+
panel.setAttribute("aria-modal", "true");
|
|
218
|
+
panel.setAttribute("aria-labelledby", "i18np-title");
|
|
187
219
|
// Mirror the popover for right-to-left locales (Arabic, Urdu, …). The i18n
|
|
188
220
|
// key stays LTR — it's a code identifier, not prose (see heading()).
|
|
189
221
|
panel.dir = config.rtl ? "rtl" : "ltr";
|
|
@@ -201,6 +233,7 @@
|
|
|
201
233
|
panel.appendChild(field(t("comment", "Comment"), commentInput));
|
|
202
234
|
|
|
203
235
|
errorNode = el("p", "i18np-error");
|
|
236
|
+
errorNode.setAttribute("role", "alert");
|
|
204
237
|
errorNode.style.display = "none";
|
|
205
238
|
panel.appendChild(errorNode);
|
|
206
239
|
|
|
@@ -208,6 +241,7 @@
|
|
|
208
241
|
|
|
209
242
|
overlay.appendChild(panel);
|
|
210
243
|
document.body.appendChild(overlay);
|
|
244
|
+
lockScroll();
|
|
211
245
|
proposedInput.focus();
|
|
212
246
|
loadPrior(key);
|
|
213
247
|
}
|
|
@@ -216,9 +250,25 @@
|
|
|
216
250
|
if (overlay) {
|
|
217
251
|
overlay.remove();
|
|
218
252
|
overlay = null;
|
|
253
|
+
unlockScroll();
|
|
254
|
+
if (openerNode && document.body.contains(openerNode)) openerNode.focus();
|
|
255
|
+
openerNode = null;
|
|
219
256
|
}
|
|
220
257
|
}
|
|
221
258
|
|
|
259
|
+
// Scroll-lock the page behind the modal, remembering the inline value so
|
|
260
|
+
// close() can put it back exactly as it was.
|
|
261
|
+
function lockScroll() {
|
|
262
|
+
if (savedOverflow === null) savedOverflow = document.documentElement.style.overflow;
|
|
263
|
+
document.documentElement.style.overflow = "hidden";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function unlockScroll() {
|
|
267
|
+
if (savedOverflow === null) return;
|
|
268
|
+
document.documentElement.style.overflow = savedOverflow;
|
|
269
|
+
savedOverflow = null;
|
|
270
|
+
}
|
|
271
|
+
|
|
222
272
|
function loadPrior(key) {
|
|
223
273
|
var params = new URLSearchParams({ key: key, locale: config.locale });
|
|
224
274
|
fetch(config.endpoint + "/context?" + params.toString(), { headers: { Accept: "application/json" } })
|
|
@@ -311,7 +361,7 @@
|
|
|
311
361
|
})
|
|
312
362
|
.catch(function () {
|
|
313
363
|
saveButton.disabled = false;
|
|
314
|
-
showError("Could not save the suggestion.");
|
|
364
|
+
showError(t("errorSave", "Could not save the suggestion."));
|
|
315
365
|
});
|
|
316
366
|
}
|
|
317
367
|
|
|
@@ -325,6 +375,7 @@
|
|
|
325
375
|
function heading(key) {
|
|
326
376
|
var wrapper = el("div", "i18np-heading");
|
|
327
377
|
var title = el("p", "i18np-title");
|
|
378
|
+
title.id = "i18np-title"; // aria-labelledby target; only one dialog at a time
|
|
328
379
|
title.textContent = t("title", "Suggest a translation fix");
|
|
329
380
|
var code = el("code", "i18np-key");
|
|
330
381
|
code.dir = "ltr"; // the key is a code path, never RTL prose
|