svelte-dnd-action 0.9.76 → 0.9.77
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.
- package/README.md +28 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.js +169 -19
- package/dist/index.mjs +169 -20
- package/package.json +1 -1
- package/src/helpers/aria.js +99 -7
- package/src/index.js +1 -1
- package/src/keyboardAction.js +44 -11
package/README.md
CHANGED
|
@@ -118,7 +118,7 @@ An options-object with the following attributes:
|
|
|
118
118
|
| `dropTargetStyle` | Object<String> | No | `{outline: 'rgba(255, 255, 102, 0.7) solid 2px'}` | An object of styles to apply to the dnd-zone when items can be dragged into it. Note: the styles override any inline styles applied to the dnd-zone. When the styles are removed, any original inline styles will be lost |
|
|
119
119
|
| `dropTargetClasses`| Array<String> | No | `[]` | A list of classes to apply to the dnd-zone when items can be dragged into it. Note: make sure the classes you use are global. |
|
|
120
120
|
| `transformDraggedElement` | Function | No | `() => {}` | A function that is invoked when the draggable element enters the dnd-zone or hover overs a new index in the current dnd-zone. <br />Signature:<br />function(element, data, index) {}<br />**element**: The dragged element. <br />**data**: The data of the item from the items array.<br />**index**: The index the dragged element will become in the new dnd-zone.<br /><br />This allows you to override properties on the dragged element, such as innerHTML to change how it displays. If what you are after is altering styles, do it to the children, not to the dragged element itself |
|
|
121
|
-
| `autoAriaDisabled` | Boolean | No | `false` | Setting it to true will disable all the automatically added aria attributes and aria alerts (for example when the user starts/ stops dragging using the keyboard).<br /> **Use it only if you intend to implement your own custom instructions, roles and alerts.** In such a case, you might find the exported function `alertToScreenReader(string)` useful. |
|
|
121
|
+
| `autoAriaDisabled` | Boolean | No | `false` | Setting it to true will disable all the automatically added aria attributes and aria alerts (for example when the user starts/ stops dragging using the keyboard).<br /> **Use it only if you intend to implement your own custom instructions, roles and alerts.** In such a case, you might find the exported function `alertToScreenReader(string)` useful. <br />**If you only want to change the wording (ex: to translate it), use `setAriaStrings` instead and leave this off.** |
|
|
122
122
|
| `centreDraggedOnCursor` | Boolean | No | `false` | Setting it to true will cause elements from this dnd-zone to position their center on the cursor on drag start, effectively turning the cursor to the focal point that triggers all the dnd events (ex: entering another zone). Useful for dnd-zones with large items that can be dragged over small items. |
|
|
123
123
|
| `useCursorForDetection` | Boolean | No | `false` | Setting it to true will use the cursor position instead of the dragged element's center for drop zone detection. This improves accuracy when dragging large elements over small drop targets. Unlike `centreDraggedOnCursor`, this option does not reposition the dragged element. |
|
|
124
124
|
| `dropAnimationDisabled` | Boolean | No | `false` | Setting it to true will disable the animation of the dropped element to its final place. |
|
|
@@ -167,6 +167,33 @@ If you don't provide the aria-labels everything will still work, but the message
|
|
|
167
167
|
_Note_: in general you probably want to use semantic-html (ex: `ol` and `li` elements rather than `section` and `div`) but the library is screen readers friendly regardless (or at least that's the goal :)).
|
|
168
168
|
If you want to implement your own custom screen-reader alerts, roles and instructions, you can use the `autoAriaDisabled` options and wire everything up yourself using markup and the `consider` and `finalize` handlers (for example: [unsortable list](https://svelte.dev/playground/e020ea1051dc4ae3ac2b697064f234bc?version=3)).
|
|
169
169
|
|
|
170
|
+
#### Translating the screen-reader messages
|
|
171
|
+
|
|
172
|
+
`autoAriaDisabled` disables all automatically added ARIA attributes, roles, instructions and alerts. If you only want to translate the screen-reader wording, import `setAriaStrings` and leave `autoAriaDisabled` off:
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
import {setAriaStrings} from "svelte-dnd-action";
|
|
176
|
+
|
|
177
|
+
setAriaStrings({
|
|
178
|
+
dragStarted: ({itemLabel, zoneLabel, canMoveBetweenZones}) =>
|
|
179
|
+
`Déplacement de ${itemLabel} commencé. Utilisez les flèches pour le déplacer dans la liste ${zoneLabel}` +
|
|
180
|
+
(canMoveBetweenZones ? `, ou tabulez vers une autre liste` : ``),
|
|
181
|
+
movedToPosition: ({itemLabel, zoneLabel, position}) => `${itemLabel} déplacé en position ${position} dans la liste ${zoneLabel}`,
|
|
182
|
+
movedToZoneEnd: ({itemLabel, zoneLabel}) => `${itemLabel} déplacé à la fin de la liste ${zoneLabel}`,
|
|
183
|
+
movedToZoneStart: ({itemLabel, zoneLabel}) => `${itemLabel} déplacé au début de la liste ${zoneLabel}`,
|
|
184
|
+
// The default message omits the destination, but custom messages can include it
|
|
185
|
+
dropped: ({itemLabel, zoneLabel, position, count}) => `${itemLabel} déposé dans ${zoneLabel}, ${position} sur ${count}`,
|
|
186
|
+
zoneActiveInstruction: `Tabulez jusqu'à un élément et appuyez sur espace ou entrée pour le déplacer`,
|
|
187
|
+
zoneDragDisabledInstruction: `Cette liste de glisser-déposer est désactivée`
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Every key is optional. Omitted keys use their English defaults. The five announcement keys are formatter functions, so translations can control word order and pluralisation. Each formatter receives `itemLabel` and `zoneLabel` from the consumer-provided `aria-label` attributes, plus `position` and `count` for the item's 1-based position and the number of items in the relevant zone. `dragStarted` also receives `canMoveBetweenZones`. Destructure only the fields your wording needs; the built-in English messages do not use every field, but custom messages can.
|
|
192
|
+
|
|
193
|
+
Call `setAriaStrings` during app-level initialization and again whenever the application locale changes. Each call defines the complete active locale by applying its overrides to the English defaults, so omitted keys return to English rather than retaining values from the previous locale. Existing instruction elements update immediately. Pass `null` to restore all English defaults. Unknown keys and values of the wrong type throw an error.
|
|
194
|
+
|
|
195
|
+
The setting is global to the document and applies to every dndzone. Different zones cannot use different strings at the same time.
|
|
196
|
+
|
|
170
197
|
##### Keyboard support
|
|
171
198
|
|
|
172
199
|
- Tab into a dnd container to get a description and instructions
|
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,27 @@ export interface DndZoneAttributes<T> {
|
|
|
82
82
|
*/
|
|
83
83
|
export declare function alertToScreenReader(txt: string): void;
|
|
84
84
|
|
|
85
|
+
// Every message key receives the same core context - itemLabel, zoneLabel, position, count -
|
|
86
|
+
// so a consumer can word any of them positionally. Only dragStarted carries an extra.
|
|
87
|
+
export interface AriaStrings {
|
|
88
|
+
dragStarted?: (ctx: {itemLabel: string; zoneLabel: string; position: number; count: number; canMoveBetweenZones: boolean}) => string;
|
|
89
|
+
movedToPosition?: (ctx: {itemLabel: string; zoneLabel: string; position: number; count: number}) => string;
|
|
90
|
+
movedToZoneEnd?: (ctx: {itemLabel: string; zoneLabel: string; position: number; count: number}) => string;
|
|
91
|
+
movedToZoneStart?: (ctx: {itemLabel: string; zoneLabel: string; position: number; count: number}) => string;
|
|
92
|
+
dropped?: (ctx: {itemLabel: string; zoneLabel: string; position: number; count: number}) => string;
|
|
93
|
+
zoneActiveInstruction?: string;
|
|
94
|
+
zoneDragDisabledInstruction?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Overrides the strings the library speaks to screen readers. Merges over the built-in English defaults,
|
|
99
|
+
* so each call describes a whole locale rather than patching the previous one - anything a call leaves
|
|
100
|
+
* out goes back to English.
|
|
101
|
+
* This is global and applies to all dndzones.
|
|
102
|
+
* Pass null to restore the built-in English strings.
|
|
103
|
+
*/
|
|
104
|
+
export declare function setAriaStrings(overrides: AriaStrings | null): void;
|
|
105
|
+
|
|
85
106
|
/**
|
|
86
107
|
* Allows using another key instead of "id" in the items data. This is global and applies to all dndzones.
|
|
87
108
|
* Has to be called when there are no rendered dndzones whatsoever.
|
package/dist/index.js
CHANGED
|
@@ -2207,12 +2207,45 @@
|
|
|
2207
2207
|
};
|
|
2208
2208
|
}
|
|
2209
2209
|
|
|
2210
|
-
var
|
|
2210
|
+
var _INSTRUCTION_ID_TO_ST;
|
|
2211
2211
|
var INSTRUCTION_IDs$1 = {
|
|
2212
2212
|
DND_ZONE_ACTIVE: "dnd-zone-active",
|
|
2213
2213
|
DND_ZONE_DRAG_DISABLED: "dnd-zone-drag-disabled"
|
|
2214
2214
|
};
|
|
2215
|
-
var
|
|
2215
|
+
var INSTRUCTION_ID_TO_STRING_KEY = (_INSTRUCTION_ID_TO_ST = {}, _defineProperty(_INSTRUCTION_ID_TO_ST, INSTRUCTION_IDs$1.DND_ZONE_ACTIVE, "zoneActiveInstruction"), _defineProperty(_INSTRUCTION_ID_TO_ST, INSTRUCTION_IDs$1.DND_ZONE_DRAG_DISABLED, "zoneDragDisabledInstruction"), _INSTRUCTION_ID_TO_ST);
|
|
2216
|
+
var DEFAULT_ARIA_STRINGS = {
|
|
2217
|
+
dragStarted: function dragStarted(_ref) {
|
|
2218
|
+
var itemLabel = _ref.itemLabel,
|
|
2219
|
+
zoneLabel = _ref.zoneLabel,
|
|
2220
|
+
canMoveBetweenZones = _ref.canMoveBetweenZones;
|
|
2221
|
+
return "Started dragging item ".concat(itemLabel, ". Use the arrow keys to move it within its list ").concat(zoneLabel) + (canMoveBetweenZones ? ", or tab to another list in order to move the item into it" : "");
|
|
2222
|
+
},
|
|
2223
|
+
movedToPosition: function movedToPosition(_ref2) {
|
|
2224
|
+
var itemLabel = _ref2.itemLabel,
|
|
2225
|
+
zoneLabel = _ref2.zoneLabel,
|
|
2226
|
+
position = _ref2.position;
|
|
2227
|
+
return "Moved item ".concat(itemLabel, " to position ").concat(position, " in the list ").concat(zoneLabel);
|
|
2228
|
+
},
|
|
2229
|
+
movedToZoneEnd: function movedToZoneEnd(_ref3) {
|
|
2230
|
+
var itemLabel = _ref3.itemLabel,
|
|
2231
|
+
zoneLabel = _ref3.zoneLabel;
|
|
2232
|
+
return "Moved item ".concat(itemLabel, " to the end of the list ").concat(zoneLabel);
|
|
2233
|
+
},
|
|
2234
|
+
movedToZoneStart: function movedToZoneStart(_ref4) {
|
|
2235
|
+
var itemLabel = _ref4.itemLabel,
|
|
2236
|
+
zoneLabel = _ref4.zoneLabel;
|
|
2237
|
+
return "Moved item ".concat(itemLabel, " to the beginning of the list ").concat(zoneLabel);
|
|
2238
|
+
},
|
|
2239
|
+
dropped: function dropped(_ref5) {
|
|
2240
|
+
var itemLabel = _ref5.itemLabel;
|
|
2241
|
+
return "Stopped dragging item ".concat(itemLabel);
|
|
2242
|
+
},
|
|
2243
|
+
zoneActiveInstruction: "Tab to one the items and press space-bar or enter to start dragging it",
|
|
2244
|
+
zoneDragDisabledInstruction: "This is a disabled drag and drop list"
|
|
2245
|
+
};
|
|
2246
|
+
var FUNCTION_ARIA_STRING_KEYS = ["dragStarted", "movedToPosition", "movedToZoneEnd", "movedToZoneStart", "dropped"];
|
|
2247
|
+
var STRING_ARIA_STRING_KEYS = ["zoneActiveInstruction", "zoneDragDisabledInstruction"];
|
|
2248
|
+
var ariaStrings = _objectSpread2({}, DEFAULT_ARIA_STRINGS);
|
|
2216
2249
|
var ALERT_DIV_ID = "dnd-action-aria-alert";
|
|
2217
2250
|
var alertsDiv;
|
|
2218
2251
|
function initAriaOnBrowser() {
|
|
@@ -2238,11 +2271,11 @@
|
|
|
2238
2271
|
document.body.prepend(alertsDiv);
|
|
2239
2272
|
|
|
2240
2273
|
// setting the instructions
|
|
2241
|
-
Object.entries(
|
|
2242
|
-
var
|
|
2243
|
-
id =
|
|
2244
|
-
|
|
2245
|
-
return document.body.prepend(instructionToHiddenDiv(id,
|
|
2274
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (_ref6) {
|
|
2275
|
+
var _ref7 = _slicedToArray(_ref6, 2),
|
|
2276
|
+
id = _ref7[0],
|
|
2277
|
+
key = _ref7[1];
|
|
2278
|
+
return document.body.prepend(instructionToHiddenDiv(id, ariaStrings[key]));
|
|
2246
2279
|
});
|
|
2247
2280
|
}
|
|
2248
2281
|
|
|
@@ -2265,7 +2298,7 @@
|
|
|
2265
2298
|
*/
|
|
2266
2299
|
function destroyAria() {
|
|
2267
2300
|
if (isOnServer || !alertsDiv) return;
|
|
2268
|
-
Object.keys(
|
|
2301
|
+
Object.keys(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (id) {
|
|
2269
2302
|
var _document$getElementB;
|
|
2270
2303
|
return (_document$getElementB = document.getElementById(id)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.remove();
|
|
2271
2304
|
});
|
|
@@ -2275,12 +2308,18 @@
|
|
|
2275
2308
|
function instructionToHiddenDiv(id, txt) {
|
|
2276
2309
|
var div = document.createElement("div");
|
|
2277
2310
|
div.id = id;
|
|
2278
|
-
div
|
|
2311
|
+
renderInstruction(div, txt);
|
|
2279
2312
|
div.style.display = "none";
|
|
2280
2313
|
div.style.position = "fixed";
|
|
2281
2314
|
div.style.zIndex = "-5";
|
|
2282
2315
|
return div;
|
|
2283
2316
|
}
|
|
2317
|
+
function renderInstruction(div, txt) {
|
|
2318
|
+
div.replaceChildren();
|
|
2319
|
+
var paragraph = document.createElement("p");
|
|
2320
|
+
paragraph.textContent = txt;
|
|
2321
|
+
div.appendChild(paragraph);
|
|
2322
|
+
}
|
|
2284
2323
|
|
|
2285
2324
|
/**
|
|
2286
2325
|
* Will make the screen reader alert the provided text to the user
|
|
@@ -2299,6 +2338,79 @@
|
|
|
2299
2338
|
alertsDiv.style.display = "inline";
|
|
2300
2339
|
}
|
|
2301
2340
|
|
|
2341
|
+
/**
|
|
2342
|
+
* Overrides the strings the library announces to screen readers. Each call starts with the built-in
|
|
2343
|
+
* English defaults and applies the supplied overrides, so omitted keys return to English when the locale
|
|
2344
|
+
* changes. Existing instruction elements update immediately. This setting is global to all dndzones.
|
|
2345
|
+
* Pass null to restore the built-in English strings.
|
|
2346
|
+
* @param {Object | null} overrides - any subset of: dragStarted, movedToPosition, movedToZoneEnd,
|
|
2347
|
+
* movedToZoneStart, dropped (functions taking a context object and returning a string);
|
|
2348
|
+
* zoneActiveInstruction, zoneDragDisabledInstruction (strings)
|
|
2349
|
+
* @throws {Error} if overrides is not an object or null, contains an unknown key, or contains a value of
|
|
2350
|
+
* the wrong type. Validation completes before the active strings change.
|
|
2351
|
+
*/
|
|
2352
|
+
function setAriaStrings(overrides) {
|
|
2353
|
+
if (overrides === null || overrides === undefined) {
|
|
2354
|
+
ariaStrings = _objectSpread2({}, DEFAULT_ARIA_STRINGS);
|
|
2355
|
+
} else {
|
|
2356
|
+
if (_typeof(overrides) !== "object" || Array.isArray(overrides)) {
|
|
2357
|
+
throw new Error("setAriaStrings expects an object or null but instead got a ".concat(_typeof(overrides), ", ").concat(toString(overrides)));
|
|
2358
|
+
}
|
|
2359
|
+
Object.keys(overrides).forEach(function (key) {
|
|
2360
|
+
if (!Object.prototype.hasOwnProperty.call(DEFAULT_ARIA_STRINGS, key)) {
|
|
2361
|
+
throw new Error("Can't set non existing aria string ".concat(key, "! Supported strings: ").concat(Object.keys(DEFAULT_ARIA_STRINGS)));
|
|
2362
|
+
}
|
|
2363
|
+
var value = overrides[key];
|
|
2364
|
+
if (FUNCTION_ARIA_STRING_KEYS.includes(key) && typeof value !== "function") {
|
|
2365
|
+
throw new Error("".concat(key, " should be a function but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2366
|
+
}
|
|
2367
|
+
if (STRING_ARIA_STRING_KEYS.includes(key) && typeof value !== "string") {
|
|
2368
|
+
throw new Error("".concat(key, " should be a string but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2369
|
+
}
|
|
2370
|
+
});
|
|
2371
|
+
ariaStrings = _objectSpread2(_objectSpread2({}, DEFAULT_ARIA_STRINGS), overrides);
|
|
2372
|
+
}
|
|
2373
|
+
if (isOnServer) return;
|
|
2374
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (_ref8) {
|
|
2375
|
+
var _ref9 = _slicedToArray(_ref8, 2),
|
|
2376
|
+
id = _ref9[0],
|
|
2377
|
+
key = _ref9[1];
|
|
2378
|
+
var div = document.getElementById(id);
|
|
2379
|
+
if (div) renderInstruction(div, ariaStrings[key]);
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
function formatAriaString(strings, key, ctx) {
|
|
2383
|
+
var ariaString = strings[key];
|
|
2384
|
+
return typeof ariaString === "function" ? ariaString(ctx) : ariaString;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
/**
|
|
2388
|
+
* Formats and announces one configured ARIA message. If a consumer formatter throws, the built-in
|
|
2389
|
+
* English formatter is used and the error is reported through printDebug. Formatter errors must not
|
|
2390
|
+
* escape because this function runs during the drag lifecycle.
|
|
2391
|
+
* @param {string} key - one of the keys accepted by setAriaStrings
|
|
2392
|
+
* @param {Object} [ctx] - the interpolation context for that key
|
|
2393
|
+
*/
|
|
2394
|
+
function announceToScreenReader(key, ctx) {
|
|
2395
|
+
var text;
|
|
2396
|
+
try {
|
|
2397
|
+
text = formatAriaString(ariaStrings, key, ctx);
|
|
2398
|
+
} catch (err) {
|
|
2399
|
+
printDebug(function () {
|
|
2400
|
+
return ["aria string formatter for \"".concat(key, "\" threw, falling back to the default"), err];
|
|
2401
|
+
});
|
|
2402
|
+
try {
|
|
2403
|
+
text = formatAriaString(DEFAULT_ARIA_STRINGS, key, ctx);
|
|
2404
|
+
} catch (defaultErr) {
|
|
2405
|
+
printDebug(function () {
|
|
2406
|
+
return ["default aria string formatter for \"".concat(key, "\" also threw"), defaultErr];
|
|
2407
|
+
});
|
|
2408
|
+
text = "";
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
alertToScreenReader(text);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2302
2414
|
var DEFAULT_DROP_ZONE_TYPE = "--any--";
|
|
2303
2415
|
var DEFAULT_DROP_TARGET_STYLE = {
|
|
2304
2416
|
outline: "rgba(255, 255, 102, 0.7) solid 2px"
|
|
@@ -2428,12 +2540,22 @@
|
|
|
2428
2540
|
if (newlyFocusedDz.getBoundingClientRect().top < focusedDz.getBoundingClientRect().top || newlyFocusedDz.getBoundingClientRect().left < focusedDz.getBoundingClientRect().left) {
|
|
2429
2541
|
targetItems.push(itemToMove);
|
|
2430
2542
|
if (!autoAriaDisabled) {
|
|
2431
|
-
|
|
2543
|
+
announceToScreenReader("movedToZoneEnd", {
|
|
2544
|
+
itemLabel: focusedItemLabel,
|
|
2545
|
+
zoneLabel: focusedDzLabel,
|
|
2546
|
+
position: targetItems.length,
|
|
2547
|
+
count: targetItems.length
|
|
2548
|
+
});
|
|
2432
2549
|
}
|
|
2433
2550
|
} else {
|
|
2434
2551
|
targetItems.unshift(itemToMove);
|
|
2435
2552
|
if (!autoAriaDisabled) {
|
|
2436
|
-
|
|
2553
|
+
announceToScreenReader("movedToZoneStart", {
|
|
2554
|
+
itemLabel: focusedItemLabel,
|
|
2555
|
+
zoneLabel: focusedDzLabel,
|
|
2556
|
+
position: 1,
|
|
2557
|
+
count: targetItems.length
|
|
2558
|
+
});
|
|
2437
2559
|
}
|
|
2438
2560
|
}
|
|
2439
2561
|
var dzFrom = focusedDz;
|
|
@@ -2470,7 +2592,17 @@
|
|
|
2470
2592
|
var droppedItemType = draggedItemType;
|
|
2471
2593
|
if (!droppedConfig) return;
|
|
2472
2594
|
if (!droppedConfig.autoAriaDisabled) {
|
|
2473
|
-
|
|
2595
|
+
// Include the destination and final position so localized messages can describe the completed drop.
|
|
2596
|
+
var droppedItems = droppedConfig.items;
|
|
2597
|
+
var droppedIdx = droppedItems.findIndex(function (item) {
|
|
2598
|
+
return item[ITEM_ID_KEY] === droppedItemId;
|
|
2599
|
+
});
|
|
2600
|
+
announceToScreenReader("dropped", {
|
|
2601
|
+
itemLabel: focusedItemLabel,
|
|
2602
|
+
zoneLabel: focusedDzLabel,
|
|
2603
|
+
position: (droppedIdx < 0 ? 0 : droppedIdx) + 1,
|
|
2604
|
+
count: droppedItems.length
|
|
2605
|
+
});
|
|
2474
2606
|
}
|
|
2475
2607
|
if (allDragTargets.has(document.activeElement)) {
|
|
2476
2608
|
document.activeElement.blur();
|
|
@@ -2557,7 +2689,12 @@
|
|
|
2557
2689
|
});
|
|
2558
2690
|
if (idx < children.length - 1) {
|
|
2559
2691
|
if (!config.autoAriaDisabled) {
|
|
2560
|
-
|
|
2692
|
+
announceToScreenReader("movedToPosition", {
|
|
2693
|
+
itemLabel: focusedItemLabel,
|
|
2694
|
+
zoneLabel: focusedDzLabel,
|
|
2695
|
+
position: idx + 2,
|
|
2696
|
+
count: items.length
|
|
2697
|
+
});
|
|
2561
2698
|
}
|
|
2562
2699
|
swap(items, idx, idx + 1);
|
|
2563
2700
|
dispatchFinalizeEvent(node, items, {
|
|
@@ -2583,7 +2720,12 @@
|
|
|
2583
2720
|
});
|
|
2584
2721
|
if (_idx > 0) {
|
|
2585
2722
|
if (!config.autoAriaDisabled) {
|
|
2586
|
-
|
|
2723
|
+
announceToScreenReader("movedToPosition", {
|
|
2724
|
+
itemLabel: focusedItemLabel,
|
|
2725
|
+
zoneLabel: focusedDzLabel,
|
|
2726
|
+
position: _idx,
|
|
2727
|
+
count: _items.length
|
|
2728
|
+
});
|
|
2587
2729
|
}
|
|
2588
2730
|
swap(_items, _idx, _idx - 1);
|
|
2589
2731
|
dispatchFinalizeEvent(node, _items, {
|
|
@@ -2614,11 +2756,18 @@
|
|
|
2614
2756
|
return dzToConfig.get(dz).dropTargetClasses;
|
|
2615
2757
|
});
|
|
2616
2758
|
if (!config.autoAriaDisabled) {
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2759
|
+
// Include the starting position so localized messages can describe where the item was picked up.
|
|
2760
|
+
var startItems = dzToConfig.get(node).items;
|
|
2761
|
+
var startIdx = startItems.findIndex(function (item) {
|
|
2762
|
+
return item[ITEM_ID_KEY] === focusedItemId;
|
|
2763
|
+
});
|
|
2764
|
+
announceToScreenReader("dragStarted", {
|
|
2765
|
+
itemLabel: focusedItemLabel,
|
|
2766
|
+
zoneLabel: focusedDzLabel,
|
|
2767
|
+
position: (startIdx < 0 ? 0 : startIdx) + 1,
|
|
2768
|
+
count: startItems.length,
|
|
2769
|
+
canMoveBetweenZones: dropTargets.length > 1
|
|
2770
|
+
});
|
|
2622
2771
|
}
|
|
2623
2772
|
dispatchConsiderEvent(node, dzToConfig.get(node).items, {
|
|
2624
2773
|
trigger: TRIGGERS.DRAG_STARTED,
|
|
@@ -3043,6 +3192,7 @@
|
|
|
3043
3192
|
exports.dragHandle = dragHandle;
|
|
3044
3193
|
exports.dragHandleZone = dragHandleZone;
|
|
3045
3194
|
exports.overrideItemIdKeyNameBeforeInitialisingDndZones = overrideItemIdKeyNameBeforeInitialisingDndZones;
|
|
3195
|
+
exports.setAriaStrings = setAriaStrings;
|
|
3046
3196
|
exports.setDebugMode = setDebugMode;
|
|
3047
3197
|
exports.setFeatureFlag = setFeatureFlag;
|
|
3048
3198
|
|
package/dist/index.mjs
CHANGED
|
@@ -2201,12 +2201,45 @@ function dndzone$2(node, options) {
|
|
|
2201
2201
|
};
|
|
2202
2202
|
}
|
|
2203
2203
|
|
|
2204
|
-
var
|
|
2204
|
+
var _INSTRUCTION_ID_TO_ST;
|
|
2205
2205
|
var INSTRUCTION_IDs$1 = {
|
|
2206
2206
|
DND_ZONE_ACTIVE: "dnd-zone-active",
|
|
2207
2207
|
DND_ZONE_DRAG_DISABLED: "dnd-zone-drag-disabled"
|
|
2208
2208
|
};
|
|
2209
|
-
var
|
|
2209
|
+
var INSTRUCTION_ID_TO_STRING_KEY = (_INSTRUCTION_ID_TO_ST = {}, _defineProperty(_INSTRUCTION_ID_TO_ST, INSTRUCTION_IDs$1.DND_ZONE_ACTIVE, "zoneActiveInstruction"), _defineProperty(_INSTRUCTION_ID_TO_ST, INSTRUCTION_IDs$1.DND_ZONE_DRAG_DISABLED, "zoneDragDisabledInstruction"), _INSTRUCTION_ID_TO_ST);
|
|
2210
|
+
var DEFAULT_ARIA_STRINGS = {
|
|
2211
|
+
dragStarted: function dragStarted(_ref) {
|
|
2212
|
+
var itemLabel = _ref.itemLabel,
|
|
2213
|
+
zoneLabel = _ref.zoneLabel,
|
|
2214
|
+
canMoveBetweenZones = _ref.canMoveBetweenZones;
|
|
2215
|
+
return "Started dragging item ".concat(itemLabel, ". Use the arrow keys to move it within its list ").concat(zoneLabel) + (canMoveBetweenZones ? ", or tab to another list in order to move the item into it" : "");
|
|
2216
|
+
},
|
|
2217
|
+
movedToPosition: function movedToPosition(_ref2) {
|
|
2218
|
+
var itemLabel = _ref2.itemLabel,
|
|
2219
|
+
zoneLabel = _ref2.zoneLabel,
|
|
2220
|
+
position = _ref2.position;
|
|
2221
|
+
return "Moved item ".concat(itemLabel, " to position ").concat(position, " in the list ").concat(zoneLabel);
|
|
2222
|
+
},
|
|
2223
|
+
movedToZoneEnd: function movedToZoneEnd(_ref3) {
|
|
2224
|
+
var itemLabel = _ref3.itemLabel,
|
|
2225
|
+
zoneLabel = _ref3.zoneLabel;
|
|
2226
|
+
return "Moved item ".concat(itemLabel, " to the end of the list ").concat(zoneLabel);
|
|
2227
|
+
},
|
|
2228
|
+
movedToZoneStart: function movedToZoneStart(_ref4) {
|
|
2229
|
+
var itemLabel = _ref4.itemLabel,
|
|
2230
|
+
zoneLabel = _ref4.zoneLabel;
|
|
2231
|
+
return "Moved item ".concat(itemLabel, " to the beginning of the list ").concat(zoneLabel);
|
|
2232
|
+
},
|
|
2233
|
+
dropped: function dropped(_ref5) {
|
|
2234
|
+
var itemLabel = _ref5.itemLabel;
|
|
2235
|
+
return "Stopped dragging item ".concat(itemLabel);
|
|
2236
|
+
},
|
|
2237
|
+
zoneActiveInstruction: "Tab to one the items and press space-bar or enter to start dragging it",
|
|
2238
|
+
zoneDragDisabledInstruction: "This is a disabled drag and drop list"
|
|
2239
|
+
};
|
|
2240
|
+
var FUNCTION_ARIA_STRING_KEYS = ["dragStarted", "movedToPosition", "movedToZoneEnd", "movedToZoneStart", "dropped"];
|
|
2241
|
+
var STRING_ARIA_STRING_KEYS = ["zoneActiveInstruction", "zoneDragDisabledInstruction"];
|
|
2242
|
+
var ariaStrings = _objectSpread2({}, DEFAULT_ARIA_STRINGS);
|
|
2210
2243
|
var ALERT_DIV_ID = "dnd-action-aria-alert";
|
|
2211
2244
|
var alertsDiv;
|
|
2212
2245
|
function initAriaOnBrowser() {
|
|
@@ -2232,11 +2265,11 @@ function initAriaOnBrowser() {
|
|
|
2232
2265
|
document.body.prepend(alertsDiv);
|
|
2233
2266
|
|
|
2234
2267
|
// setting the instructions
|
|
2235
|
-
Object.entries(
|
|
2236
|
-
var
|
|
2237
|
-
id =
|
|
2238
|
-
|
|
2239
|
-
return document.body.prepend(instructionToHiddenDiv(id,
|
|
2268
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (_ref6) {
|
|
2269
|
+
var _ref7 = _slicedToArray(_ref6, 2),
|
|
2270
|
+
id = _ref7[0],
|
|
2271
|
+
key = _ref7[1];
|
|
2272
|
+
return document.body.prepend(instructionToHiddenDiv(id, ariaStrings[key]));
|
|
2240
2273
|
});
|
|
2241
2274
|
}
|
|
2242
2275
|
|
|
@@ -2259,7 +2292,7 @@ function initAria() {
|
|
|
2259
2292
|
*/
|
|
2260
2293
|
function destroyAria() {
|
|
2261
2294
|
if (isOnServer || !alertsDiv) return;
|
|
2262
|
-
Object.keys(
|
|
2295
|
+
Object.keys(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (id) {
|
|
2263
2296
|
var _document$getElementB;
|
|
2264
2297
|
return (_document$getElementB = document.getElementById(id)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.remove();
|
|
2265
2298
|
});
|
|
@@ -2269,12 +2302,18 @@ function destroyAria() {
|
|
|
2269
2302
|
function instructionToHiddenDiv(id, txt) {
|
|
2270
2303
|
var div = document.createElement("div");
|
|
2271
2304
|
div.id = id;
|
|
2272
|
-
div
|
|
2305
|
+
renderInstruction(div, txt);
|
|
2273
2306
|
div.style.display = "none";
|
|
2274
2307
|
div.style.position = "fixed";
|
|
2275
2308
|
div.style.zIndex = "-5";
|
|
2276
2309
|
return div;
|
|
2277
2310
|
}
|
|
2311
|
+
function renderInstruction(div, txt) {
|
|
2312
|
+
div.replaceChildren();
|
|
2313
|
+
var paragraph = document.createElement("p");
|
|
2314
|
+
paragraph.textContent = txt;
|
|
2315
|
+
div.appendChild(paragraph);
|
|
2316
|
+
}
|
|
2278
2317
|
|
|
2279
2318
|
/**
|
|
2280
2319
|
* Will make the screen reader alert the provided text to the user
|
|
@@ -2293,6 +2332,79 @@ function alertToScreenReader(txt) {
|
|
|
2293
2332
|
alertsDiv.style.display = "inline";
|
|
2294
2333
|
}
|
|
2295
2334
|
|
|
2335
|
+
/**
|
|
2336
|
+
* Overrides the strings the library announces to screen readers. Each call starts with the built-in
|
|
2337
|
+
* English defaults and applies the supplied overrides, so omitted keys return to English when the locale
|
|
2338
|
+
* changes. Existing instruction elements update immediately. This setting is global to all dndzones.
|
|
2339
|
+
* Pass null to restore the built-in English strings.
|
|
2340
|
+
* @param {Object | null} overrides - any subset of: dragStarted, movedToPosition, movedToZoneEnd,
|
|
2341
|
+
* movedToZoneStart, dropped (functions taking a context object and returning a string);
|
|
2342
|
+
* zoneActiveInstruction, zoneDragDisabledInstruction (strings)
|
|
2343
|
+
* @throws {Error} if overrides is not an object or null, contains an unknown key, or contains a value of
|
|
2344
|
+
* the wrong type. Validation completes before the active strings change.
|
|
2345
|
+
*/
|
|
2346
|
+
function setAriaStrings(overrides) {
|
|
2347
|
+
if (overrides === null || overrides === undefined) {
|
|
2348
|
+
ariaStrings = _objectSpread2({}, DEFAULT_ARIA_STRINGS);
|
|
2349
|
+
} else {
|
|
2350
|
+
if (_typeof(overrides) !== "object" || Array.isArray(overrides)) {
|
|
2351
|
+
throw new Error("setAriaStrings expects an object or null but instead got a ".concat(_typeof(overrides), ", ").concat(toString(overrides)));
|
|
2352
|
+
}
|
|
2353
|
+
Object.keys(overrides).forEach(function (key) {
|
|
2354
|
+
if (!Object.prototype.hasOwnProperty.call(DEFAULT_ARIA_STRINGS, key)) {
|
|
2355
|
+
throw new Error("Can't set non existing aria string ".concat(key, "! Supported strings: ").concat(Object.keys(DEFAULT_ARIA_STRINGS)));
|
|
2356
|
+
}
|
|
2357
|
+
var value = overrides[key];
|
|
2358
|
+
if (FUNCTION_ARIA_STRING_KEYS.includes(key) && typeof value !== "function") {
|
|
2359
|
+
throw new Error("".concat(key, " should be a function but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2360
|
+
}
|
|
2361
|
+
if (STRING_ARIA_STRING_KEYS.includes(key) && typeof value !== "string") {
|
|
2362
|
+
throw new Error("".concat(key, " should be a string but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2363
|
+
}
|
|
2364
|
+
});
|
|
2365
|
+
ariaStrings = _objectSpread2(_objectSpread2({}, DEFAULT_ARIA_STRINGS), overrides);
|
|
2366
|
+
}
|
|
2367
|
+
if (isOnServer) return;
|
|
2368
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (_ref8) {
|
|
2369
|
+
var _ref9 = _slicedToArray(_ref8, 2),
|
|
2370
|
+
id = _ref9[0],
|
|
2371
|
+
key = _ref9[1];
|
|
2372
|
+
var div = document.getElementById(id);
|
|
2373
|
+
if (div) renderInstruction(div, ariaStrings[key]);
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
function formatAriaString(strings, key, ctx) {
|
|
2377
|
+
var ariaString = strings[key];
|
|
2378
|
+
return typeof ariaString === "function" ? ariaString(ctx) : ariaString;
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
/**
|
|
2382
|
+
* Formats and announces one configured ARIA message. If a consumer formatter throws, the built-in
|
|
2383
|
+
* English formatter is used and the error is reported through printDebug. Formatter errors must not
|
|
2384
|
+
* escape because this function runs during the drag lifecycle.
|
|
2385
|
+
* @param {string} key - one of the keys accepted by setAriaStrings
|
|
2386
|
+
* @param {Object} [ctx] - the interpolation context for that key
|
|
2387
|
+
*/
|
|
2388
|
+
function announceToScreenReader(key, ctx) {
|
|
2389
|
+
var text;
|
|
2390
|
+
try {
|
|
2391
|
+
text = formatAriaString(ariaStrings, key, ctx);
|
|
2392
|
+
} catch (err) {
|
|
2393
|
+
printDebug(function () {
|
|
2394
|
+
return ["aria string formatter for \"".concat(key, "\" threw, falling back to the default"), err];
|
|
2395
|
+
});
|
|
2396
|
+
try {
|
|
2397
|
+
text = formatAriaString(DEFAULT_ARIA_STRINGS, key, ctx);
|
|
2398
|
+
} catch (defaultErr) {
|
|
2399
|
+
printDebug(function () {
|
|
2400
|
+
return ["default aria string formatter for \"".concat(key, "\" also threw"), defaultErr];
|
|
2401
|
+
});
|
|
2402
|
+
text = "";
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
alertToScreenReader(text);
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2296
2408
|
var DEFAULT_DROP_ZONE_TYPE = "--any--";
|
|
2297
2409
|
var DEFAULT_DROP_TARGET_STYLE = {
|
|
2298
2410
|
outline: "rgba(255, 255, 102, 0.7) solid 2px"
|
|
@@ -2422,12 +2534,22 @@ function handleZoneFocus(e) {
|
|
|
2422
2534
|
if (newlyFocusedDz.getBoundingClientRect().top < focusedDz.getBoundingClientRect().top || newlyFocusedDz.getBoundingClientRect().left < focusedDz.getBoundingClientRect().left) {
|
|
2423
2535
|
targetItems.push(itemToMove);
|
|
2424
2536
|
if (!autoAriaDisabled) {
|
|
2425
|
-
|
|
2537
|
+
announceToScreenReader("movedToZoneEnd", {
|
|
2538
|
+
itemLabel: focusedItemLabel,
|
|
2539
|
+
zoneLabel: focusedDzLabel,
|
|
2540
|
+
position: targetItems.length,
|
|
2541
|
+
count: targetItems.length
|
|
2542
|
+
});
|
|
2426
2543
|
}
|
|
2427
2544
|
} else {
|
|
2428
2545
|
targetItems.unshift(itemToMove);
|
|
2429
2546
|
if (!autoAriaDisabled) {
|
|
2430
|
-
|
|
2547
|
+
announceToScreenReader("movedToZoneStart", {
|
|
2548
|
+
itemLabel: focusedItemLabel,
|
|
2549
|
+
zoneLabel: focusedDzLabel,
|
|
2550
|
+
position: 1,
|
|
2551
|
+
count: targetItems.length
|
|
2552
|
+
});
|
|
2431
2553
|
}
|
|
2432
2554
|
}
|
|
2433
2555
|
var dzFrom = focusedDz;
|
|
@@ -2464,7 +2586,17 @@ function handleDrop() {
|
|
|
2464
2586
|
var droppedItemType = draggedItemType;
|
|
2465
2587
|
if (!droppedConfig) return;
|
|
2466
2588
|
if (!droppedConfig.autoAriaDisabled) {
|
|
2467
|
-
|
|
2589
|
+
// Include the destination and final position so localized messages can describe the completed drop.
|
|
2590
|
+
var droppedItems = droppedConfig.items;
|
|
2591
|
+
var droppedIdx = droppedItems.findIndex(function (item) {
|
|
2592
|
+
return item[ITEM_ID_KEY] === droppedItemId;
|
|
2593
|
+
});
|
|
2594
|
+
announceToScreenReader("dropped", {
|
|
2595
|
+
itemLabel: focusedItemLabel,
|
|
2596
|
+
zoneLabel: focusedDzLabel,
|
|
2597
|
+
position: (droppedIdx < 0 ? 0 : droppedIdx) + 1,
|
|
2598
|
+
count: droppedItems.length
|
|
2599
|
+
});
|
|
2468
2600
|
}
|
|
2469
2601
|
if (allDragTargets.has(document.activeElement)) {
|
|
2470
2602
|
document.activeElement.blur();
|
|
@@ -2551,7 +2683,12 @@ function dndzone$1(node, options) {
|
|
|
2551
2683
|
});
|
|
2552
2684
|
if (idx < children.length - 1) {
|
|
2553
2685
|
if (!config.autoAriaDisabled) {
|
|
2554
|
-
|
|
2686
|
+
announceToScreenReader("movedToPosition", {
|
|
2687
|
+
itemLabel: focusedItemLabel,
|
|
2688
|
+
zoneLabel: focusedDzLabel,
|
|
2689
|
+
position: idx + 2,
|
|
2690
|
+
count: items.length
|
|
2691
|
+
});
|
|
2555
2692
|
}
|
|
2556
2693
|
swap(items, idx, idx + 1);
|
|
2557
2694
|
dispatchFinalizeEvent(node, items, {
|
|
@@ -2577,7 +2714,12 @@ function dndzone$1(node, options) {
|
|
|
2577
2714
|
});
|
|
2578
2715
|
if (_idx > 0) {
|
|
2579
2716
|
if (!config.autoAriaDisabled) {
|
|
2580
|
-
|
|
2717
|
+
announceToScreenReader("movedToPosition", {
|
|
2718
|
+
itemLabel: focusedItemLabel,
|
|
2719
|
+
zoneLabel: focusedDzLabel,
|
|
2720
|
+
position: _idx,
|
|
2721
|
+
count: _items.length
|
|
2722
|
+
});
|
|
2581
2723
|
}
|
|
2582
2724
|
swap(_items, _idx, _idx - 1);
|
|
2583
2725
|
dispatchFinalizeEvent(node, _items, {
|
|
@@ -2608,11 +2750,18 @@ function dndzone$1(node, options) {
|
|
|
2608
2750
|
return dzToConfig.get(dz).dropTargetClasses;
|
|
2609
2751
|
});
|
|
2610
2752
|
if (!config.autoAriaDisabled) {
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2753
|
+
// Include the starting position so localized messages can describe where the item was picked up.
|
|
2754
|
+
var startItems = dzToConfig.get(node).items;
|
|
2755
|
+
var startIdx = startItems.findIndex(function (item) {
|
|
2756
|
+
return item[ITEM_ID_KEY] === focusedItemId;
|
|
2757
|
+
});
|
|
2758
|
+
announceToScreenReader("dragStarted", {
|
|
2759
|
+
itemLabel: focusedItemLabel,
|
|
2760
|
+
zoneLabel: focusedDzLabel,
|
|
2761
|
+
position: (startIdx < 0 ? 0 : startIdx) + 1,
|
|
2762
|
+
count: startItems.length,
|
|
2763
|
+
canMoveBetweenZones: dropTargets.length > 1
|
|
2764
|
+
});
|
|
2616
2765
|
}
|
|
2617
2766
|
dispatchConsiderEvent(node, dzToConfig.get(node).items, {
|
|
2618
2767
|
trigger: TRIGGERS.DRAG_STARTED,
|
|
@@ -3026,4 +3175,4 @@ function dragHandle(handle) {
|
|
|
3026
3175
|
};
|
|
3027
3176
|
}
|
|
3028
3177
|
|
|
3029
|
-
export { DRAGGED_ELEMENT_ID, FEATURE_FLAG_NAMES, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID, SOURCES, TRIGGERS, alertToScreenReader, dndzone, dragHandle, dragHandleZone, overrideItemIdKeyNameBeforeInitialisingDndZones, setDebugMode, setFeatureFlag };
|
|
3178
|
+
export { DRAGGED_ELEMENT_ID, FEATURE_FLAG_NAMES, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID, SOURCES, TRIGGERS, alertToScreenReader, dndzone, dragHandle, dragHandleZone, overrideItemIdKeyNameBeforeInitialisingDndZones, setAriaStrings, setDebugMode, setFeatureFlag };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-dnd-action",
|
|
3
3
|
"description": "*An awesome drag and drop library for Svelte 3 and 4 (not using the browser's built-in dnd, thanks god): Rich animations, nested containers, touch support and more *",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.77",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/isaacHagoel/svelte-dnd-action.git"
|
package/src/helpers/aria.js
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
|
-
import {isOnServer} from "../constants";
|
|
1
|
+
import {isOnServer, printDebug} from "../constants";
|
|
2
|
+
import {toString} from "./util";
|
|
2
3
|
|
|
3
4
|
const INSTRUCTION_IDs = {
|
|
4
5
|
DND_ZONE_ACTIVE: "dnd-zone-active",
|
|
5
6
|
DND_ZONE_DRAG_DISABLED: "dnd-zone-drag-disabled"
|
|
6
7
|
};
|
|
7
|
-
const
|
|
8
|
-
[INSTRUCTION_IDs.DND_ZONE_ACTIVE]: "
|
|
9
|
-
[INSTRUCTION_IDs.DND_ZONE_DRAG_DISABLED]: "
|
|
8
|
+
const INSTRUCTION_ID_TO_STRING_KEY = {
|
|
9
|
+
[INSTRUCTION_IDs.DND_ZONE_ACTIVE]: "zoneActiveInstruction",
|
|
10
|
+
[INSTRUCTION_IDs.DND_ZONE_DRAG_DISABLED]: "zoneDragDisabledInstruction"
|
|
10
11
|
};
|
|
11
12
|
|
|
13
|
+
const DEFAULT_ARIA_STRINGS = {
|
|
14
|
+
dragStarted: ({itemLabel, zoneLabel, canMoveBetweenZones}) =>
|
|
15
|
+
`Started dragging item ${itemLabel}. Use the arrow keys to move it within its list ${zoneLabel}` +
|
|
16
|
+
(canMoveBetweenZones ? ", or tab to another list in order to move the item into it" : ""),
|
|
17
|
+
movedToPosition: ({itemLabel, zoneLabel, position}) => `Moved item ${itemLabel} to position ${position} in the list ${zoneLabel}`,
|
|
18
|
+
movedToZoneEnd: ({itemLabel, zoneLabel}) => `Moved item ${itemLabel} to the end of the list ${zoneLabel}`,
|
|
19
|
+
movedToZoneStart: ({itemLabel, zoneLabel}) => `Moved item ${itemLabel} to the beginning of the list ${zoneLabel}`,
|
|
20
|
+
dropped: ({itemLabel}) => `Stopped dragging item ${itemLabel}`,
|
|
21
|
+
zoneActiveInstruction: "Tab to one the items and press space-bar or enter to start dragging it",
|
|
22
|
+
zoneDragDisabledInstruction: "This is a disabled drag and drop list"
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const FUNCTION_ARIA_STRING_KEYS = ["dragStarted", "movedToPosition", "movedToZoneEnd", "movedToZoneStart", "dropped"];
|
|
26
|
+
const STRING_ARIA_STRING_KEYS = ["zoneActiveInstruction", "zoneDragDisabledInstruction"];
|
|
27
|
+
|
|
28
|
+
let ariaStrings = {...DEFAULT_ARIA_STRINGS};
|
|
29
|
+
|
|
12
30
|
const ALERT_DIV_ID = "dnd-action-aria-alert";
|
|
13
31
|
let alertsDiv;
|
|
14
32
|
|
|
@@ -35,7 +53,7 @@ function initAriaOnBrowser() {
|
|
|
35
53
|
document.body.prepend(alertsDiv);
|
|
36
54
|
|
|
37
55
|
// setting the instructions
|
|
38
|
-
Object.entries(
|
|
56
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(([id, key]) => document.body.prepend(instructionToHiddenDiv(id, ariaStrings[key])));
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
/**
|
|
@@ -57,7 +75,7 @@ export function initAria() {
|
|
|
57
75
|
*/
|
|
58
76
|
export function destroyAria() {
|
|
59
77
|
if (isOnServer || !alertsDiv) return;
|
|
60
|
-
Object.keys(
|
|
78
|
+
Object.keys(INSTRUCTION_ID_TO_STRING_KEY).forEach(id => document.getElementById(id)?.remove());
|
|
61
79
|
alertsDiv.remove();
|
|
62
80
|
alertsDiv = undefined;
|
|
63
81
|
}
|
|
@@ -65,13 +83,20 @@ export function destroyAria() {
|
|
|
65
83
|
function instructionToHiddenDiv(id, txt) {
|
|
66
84
|
const div = document.createElement("div");
|
|
67
85
|
div.id = id;
|
|
68
|
-
div
|
|
86
|
+
renderInstruction(div, txt);
|
|
69
87
|
div.style.display = "none";
|
|
70
88
|
div.style.position = "fixed";
|
|
71
89
|
div.style.zIndex = "-5";
|
|
72
90
|
return div;
|
|
73
91
|
}
|
|
74
92
|
|
|
93
|
+
function renderInstruction(div, txt) {
|
|
94
|
+
div.replaceChildren();
|
|
95
|
+
const paragraph = document.createElement("p");
|
|
96
|
+
paragraph.textContent = txt;
|
|
97
|
+
div.appendChild(paragraph);
|
|
98
|
+
}
|
|
99
|
+
|
|
75
100
|
/**
|
|
76
101
|
* Will make the screen reader alert the provided text to the user
|
|
77
102
|
* @param {string} txt
|
|
@@ -88,3 +113,70 @@ export function alertToScreenReader(txt) {
|
|
|
88
113
|
alertsDiv.style.display = "none";
|
|
89
114
|
alertsDiv.style.display = "inline";
|
|
90
115
|
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Overrides the strings the library announces to screen readers. Each call starts with the built-in
|
|
119
|
+
* English defaults and applies the supplied overrides, so omitted keys return to English when the locale
|
|
120
|
+
* changes. Existing instruction elements update immediately. This setting is global to all dndzones.
|
|
121
|
+
* Pass null to restore the built-in English strings.
|
|
122
|
+
* @param {Object | null} overrides - any subset of: dragStarted, movedToPosition, movedToZoneEnd,
|
|
123
|
+
* movedToZoneStart, dropped (functions taking a context object and returning a string);
|
|
124
|
+
* zoneActiveInstruction, zoneDragDisabledInstruction (strings)
|
|
125
|
+
* @throws {Error} if overrides is not an object or null, contains an unknown key, or contains a value of
|
|
126
|
+
* the wrong type. Validation completes before the active strings change.
|
|
127
|
+
*/
|
|
128
|
+
export function setAriaStrings(overrides) {
|
|
129
|
+
if (overrides === null || overrides === undefined) {
|
|
130
|
+
ariaStrings = {...DEFAULT_ARIA_STRINGS};
|
|
131
|
+
} else {
|
|
132
|
+
if (typeof overrides !== "object" || Array.isArray(overrides)) {
|
|
133
|
+
throw new Error(`setAriaStrings expects an object or null but instead got a ${typeof overrides}, ${toString(overrides)}`);
|
|
134
|
+
}
|
|
135
|
+
Object.keys(overrides).forEach(key => {
|
|
136
|
+
if (!Object.prototype.hasOwnProperty.call(DEFAULT_ARIA_STRINGS, key)) {
|
|
137
|
+
throw new Error(`Can't set non existing aria string ${key}! Supported strings: ${Object.keys(DEFAULT_ARIA_STRINGS)}`);
|
|
138
|
+
}
|
|
139
|
+
const value = overrides[key];
|
|
140
|
+
if (FUNCTION_ARIA_STRING_KEYS.includes(key) && typeof value !== "function") {
|
|
141
|
+
throw new Error(`${key} should be a function but instead it is a ${typeof value}, ${toString(value)}`);
|
|
142
|
+
}
|
|
143
|
+
if (STRING_ARIA_STRING_KEYS.includes(key) && typeof value !== "string") {
|
|
144
|
+
throw new Error(`${key} should be a string but instead it is a ${typeof value}, ${toString(value)}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
ariaStrings = {...DEFAULT_ARIA_STRINGS, ...overrides};
|
|
148
|
+
}
|
|
149
|
+
if (isOnServer) return;
|
|
150
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(([id, key]) => {
|
|
151
|
+
const div = document.getElementById(id);
|
|
152
|
+
if (div) renderInstruction(div, ariaStrings[key]);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function formatAriaString(strings, key, ctx) {
|
|
157
|
+
const ariaString = strings[key];
|
|
158
|
+
return typeof ariaString === "function" ? ariaString(ctx) : ariaString;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Formats and announces one configured ARIA message. If a consumer formatter throws, the built-in
|
|
163
|
+
* English formatter is used and the error is reported through printDebug. Formatter errors must not
|
|
164
|
+
* escape because this function runs during the drag lifecycle.
|
|
165
|
+
* @param {string} key - one of the keys accepted by setAriaStrings
|
|
166
|
+
* @param {Object} [ctx] - the interpolation context for that key
|
|
167
|
+
*/
|
|
168
|
+
export function announceToScreenReader(key, ctx) {
|
|
169
|
+
let text;
|
|
170
|
+
try {
|
|
171
|
+
text = formatAriaString(ariaStrings, key, ctx);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
printDebug(() => [`aria string formatter for "${key}" threw, falling back to the default`, err]);
|
|
174
|
+
try {
|
|
175
|
+
text = formatAriaString(DEFAULT_ARIA_STRINGS, key, ctx);
|
|
176
|
+
} catch (defaultErr) {
|
|
177
|
+
printDebug(() => [`default aria string formatter for "${key}" also threw`, defaultErr]);
|
|
178
|
+
text = "";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
alertToScreenReader(text);
|
|
182
|
+
}
|
package/src/index.js
CHANGED
package/src/keyboardAction.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {decrementActiveDropZoneCount, incrementActiveDropZoneCount, ITEM_ID_KEY, SOURCES, TRIGGERS} from "./constants";
|
|
2
2
|
import {styleActiveDropZones, styleInactiveDropZones} from "./helpers/styler";
|
|
3
3
|
import {dispatchConsiderEvent, dispatchFinalizeEvent} from "./helpers/dispatcher";
|
|
4
|
-
import {initAria,
|
|
4
|
+
import {initAria, announceToScreenReader, destroyAria} from "./helpers/aria";
|
|
5
5
|
import {toString} from "./helpers/util";
|
|
6
6
|
import {printDebug} from "./constants";
|
|
7
7
|
|
|
@@ -124,12 +124,22 @@ function handleZoneFocus(e) {
|
|
|
124
124
|
) {
|
|
125
125
|
targetItems.push(itemToMove);
|
|
126
126
|
if (!autoAriaDisabled) {
|
|
127
|
-
|
|
127
|
+
announceToScreenReader("movedToZoneEnd", {
|
|
128
|
+
itemLabel: focusedItemLabel,
|
|
129
|
+
zoneLabel: focusedDzLabel,
|
|
130
|
+
position: targetItems.length,
|
|
131
|
+
count: targetItems.length
|
|
132
|
+
});
|
|
128
133
|
}
|
|
129
134
|
} else {
|
|
130
135
|
targetItems.unshift(itemToMove);
|
|
131
136
|
if (!autoAriaDisabled) {
|
|
132
|
-
|
|
137
|
+
announceToScreenReader("movedToZoneStart", {
|
|
138
|
+
itemLabel: focusedItemLabel,
|
|
139
|
+
zoneLabel: focusedDzLabel,
|
|
140
|
+
position: 1,
|
|
141
|
+
count: targetItems.length
|
|
142
|
+
});
|
|
133
143
|
}
|
|
134
144
|
}
|
|
135
145
|
const dzFrom = focusedDz;
|
|
@@ -155,7 +165,15 @@ function handleDrop(dispatchConsider = true) {
|
|
|
155
165
|
if (!droppedConfig) return;
|
|
156
166
|
|
|
157
167
|
if (!droppedConfig.autoAriaDisabled) {
|
|
158
|
-
|
|
168
|
+
// Include the destination and final position so localized messages can describe the completed drop.
|
|
169
|
+
const droppedItems = droppedConfig.items;
|
|
170
|
+
const droppedIdx = droppedItems.findIndex(item => item[ITEM_ID_KEY] === droppedItemId);
|
|
171
|
+
announceToScreenReader("dropped", {
|
|
172
|
+
itemLabel: focusedItemLabel,
|
|
173
|
+
zoneLabel: focusedDzLabel,
|
|
174
|
+
position: (droppedIdx < 0 ? 0 : droppedIdx) + 1,
|
|
175
|
+
count: droppedItems.length
|
|
176
|
+
});
|
|
159
177
|
}
|
|
160
178
|
if (allDragTargets.has(document.activeElement)) {
|
|
161
179
|
document.activeElement.blur();
|
|
@@ -238,7 +256,12 @@ export function dndzone(node, options) {
|
|
|
238
256
|
printDebug(() => ["arrow down", idx]);
|
|
239
257
|
if (idx < children.length - 1) {
|
|
240
258
|
if (!config.autoAriaDisabled) {
|
|
241
|
-
|
|
259
|
+
announceToScreenReader("movedToPosition", {
|
|
260
|
+
itemLabel: focusedItemLabel,
|
|
261
|
+
zoneLabel: focusedDzLabel,
|
|
262
|
+
position: idx + 2,
|
|
263
|
+
count: items.length
|
|
264
|
+
});
|
|
242
265
|
}
|
|
243
266
|
swap(items, idx, idx + 1);
|
|
244
267
|
dispatchFinalizeEvent(node, items, {trigger: TRIGGERS.DROPPED_INTO_ZONE, id: focusedItemId, source: SOURCES.KEYBOARD});
|
|
@@ -256,7 +279,12 @@ export function dndzone(node, options) {
|
|
|
256
279
|
printDebug(() => ["arrow up", idx]);
|
|
257
280
|
if (idx > 0) {
|
|
258
281
|
if (!config.autoAriaDisabled) {
|
|
259
|
-
|
|
282
|
+
announceToScreenReader("movedToPosition", {
|
|
283
|
+
itemLabel: focusedItemLabel,
|
|
284
|
+
zoneLabel: focusedDzLabel,
|
|
285
|
+
position: idx,
|
|
286
|
+
count: items.length
|
|
287
|
+
});
|
|
260
288
|
}
|
|
261
289
|
swap(items, idx, idx - 1);
|
|
262
290
|
dispatchFinalizeEvent(node, items, {trigger: TRIGGERS.DROPPED_INTO_ZONE, id: focusedItemId, source: SOURCES.KEYBOARD});
|
|
@@ -279,11 +307,16 @@ export function dndzone(node, options) {
|
|
|
279
307
|
dz => dzToConfig.get(dz).dropTargetClasses
|
|
280
308
|
);
|
|
281
309
|
if (!config.autoAriaDisabled) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
310
|
+
// Include the starting position so localized messages can describe where the item was picked up.
|
|
311
|
+
const startItems = dzToConfig.get(node).items;
|
|
312
|
+
const startIdx = startItems.findIndex(item => item[ITEM_ID_KEY] === focusedItemId);
|
|
313
|
+
announceToScreenReader("dragStarted", {
|
|
314
|
+
itemLabel: focusedItemLabel,
|
|
315
|
+
zoneLabel: focusedDzLabel,
|
|
316
|
+
position: (startIdx < 0 ? 0 : startIdx) + 1,
|
|
317
|
+
count: startItems.length,
|
|
318
|
+
canMoveBetweenZones: dropTargets.length > 1
|
|
319
|
+
});
|
|
287
320
|
}
|
|
288
321
|
dispatchConsiderEvent(node, dzToConfig.get(node).items, {trigger: TRIGGERS.DRAG_STARTED, id: focusedItemId, source: SOURCES.KEYBOARD});
|
|
289
322
|
triggerAllDzsUpdate();
|