svelte-dnd-action 0.9.77 → 0.9.78
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 +38 -6
- package/dist/index.d.ts +10 -1
- package/dist/index.js +118 -32
- package/dist/index.mjs +118 -33
- package/package.json +1 -1
- package/src/constants.js +18 -0
- package/src/helpers/aria.js +52 -23
- package/src/index.js +1 -0
- package/src/keyboardAction.js +5 -0
- package/src/keyboardDragTrigger.js +44 -0
- package/src/wrappers/withDragHandles.js +2 -1
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# SVELTE DND ACTION [](https://snyk.io/test/github/isaacHagoel/svelte-dnd-action?targetFile=package.json)
|
|
2
2
|
|
|
3
|
-
> Shameless plug: If you are making AI Apps check out my brand-new, open source [dev tool](https://github.com/pragmaticfish/sushify)
|
|
4
|
-
|
|
5
3
|
This is a feature-complete implementation of drag and drop for Svelte using a custom action. It supports almost every imaginable drag and drop use-case, any input device and is fully accessible. <br />
|
|
6
4
|
It requires very minimal configuration, while offering a rich set of primitives that allow overriding basically any of its default behaviours (using the handler functions). <br /><br />
|
|
7
5
|
See full features list below. <br />
|
|
@@ -167,6 +165,26 @@ If you don't provide the aria-labels everything will still work, but the message
|
|
|
167
165
|
_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
166
|
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
167
|
|
|
168
|
+
#### Choosing the keyboard drag trigger
|
|
169
|
+
|
|
170
|
+
By default both _Space_ and _Enter_ start and stop a keyboard drag. If your app needs one of
|
|
171
|
+
those keys for something else - ex: _Enter_ to open or edit the focused item - narrow the trigger:
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
import {setKeyboardDragTrigger} from "svelte-dnd-action";
|
|
175
|
+
|
|
176
|
+
setKeyboardDragTrigger("space");
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Accepted values are `"space"`, `"enter"` and `"space_or_enter"` (the default). Pass `null` or
|
|
180
|
+
`undefined` (or call with no argument) to restore the default. Any other value throws.
|
|
181
|
+
|
|
182
|
+
Keys outside the trigger are never claimed. _Escape_ still ends a drag under every trigger.
|
|
183
|
+
|
|
184
|
+
The setting is global to the document and applies to every dndzone; different zones cannot use
|
|
185
|
+
different triggers at the same time. It can be called at any time, including after the zones have
|
|
186
|
+
rendered. The built-in screen-reader instruction follows it automatically.
|
|
187
|
+
|
|
170
188
|
#### Translating the screen-reader messages
|
|
171
189
|
|
|
172
190
|
`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:
|
|
@@ -174,6 +192,12 @@ If you want to implement your own custom screen-reader alerts, roles and instruc
|
|
|
174
192
|
```javascript
|
|
175
193
|
import {setAriaStrings} from "svelte-dnd-action";
|
|
176
194
|
|
|
195
|
+
const instructionsParTouche = {
|
|
196
|
+
space: `Tabulez jusqu'à un élément et appuyez sur espace pour le déplacer`,
|
|
197
|
+
enter: `Tabulez jusqu'à un élément et appuyez sur entrée pour le déplacer`,
|
|
198
|
+
space_or_enter: `Tabulez jusqu'à un élément et appuyez sur espace ou entrée pour le déplacer`
|
|
199
|
+
};
|
|
200
|
+
|
|
177
201
|
setAriaStrings({
|
|
178
202
|
dragStarted: ({itemLabel, zoneLabel, canMoveBetweenZones}) =>
|
|
179
203
|
`Déplacement de ${itemLabel} commencé. Utilisez les flèches pour le déplacer dans la liste ${zoneLabel}` +
|
|
@@ -183,12 +207,20 @@ setAriaStrings({
|
|
|
183
207
|
movedToZoneStart: ({itemLabel, zoneLabel}) => `${itemLabel} déplacé au début de la liste ${zoneLabel}`,
|
|
184
208
|
// The default message omits the destination, but custom messages can include it
|
|
185
209
|
dropped: ({itemLabel, zoneLabel, position, count}) => `${itemLabel} déposé dans ${zoneLabel}, ${position} sur ${count}`,
|
|
186
|
-
zoneActiveInstruction:
|
|
210
|
+
zoneActiveInstruction: ({keyboardDragTrigger}) => instructionsParTouche[keyboardDragTrigger],
|
|
187
211
|
zoneDragDisabledInstruction: `Cette liste de glisser-déposer est désactivée`
|
|
188
212
|
});
|
|
189
213
|
```
|
|
190
214
|
|
|
191
|
-
Every key is optional. Omitted keys use their English defaults. The five announcement keys are formatter
|
|
215
|
+
Every key is optional. Omitted keys use their English defaults. The five announcement keys are formatter
|
|
216
|
+
functions, so translations can control word order and pluralisation. Each formatter receives `itemLabel`
|
|
217
|
+
and `zoneLabel` from the consumer-provided `aria-label` attributes, plus `position` and `count` for the
|
|
218
|
+
item's 1-based position and the number of items in the relevant zone. `dragStarted` also receives
|
|
219
|
+
`canMoveBetweenZones`. Destructure only the fields your wording needs; the built-in English messages do not
|
|
220
|
+
use every field, but custom messages can.
|
|
221
|
+
|
|
222
|
+
`zoneActiveInstruction` takes a string or a formatter receiving the value set through
|
|
223
|
+
[`setKeyboardDragTrigger`](#choosing-the-keyboard-drag-trigger).
|
|
192
224
|
|
|
193
225
|
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
226
|
|
|
@@ -197,10 +229,10 @@ The setting is global to the document and applies to every dndzone. Different zo
|
|
|
197
229
|
##### Keyboard support
|
|
198
230
|
|
|
199
231
|
- Tab into a dnd container to get a description and instructions
|
|
200
|
-
- Tab into an item and press the _Space_/_Enter_ key to enter dragging-mode. The reader will tell the user a drag has started.
|
|
232
|
+
- Tab into an item and press the _Space_/_Enter_ key to enter dragging-mode (see `setKeyboardDragTrigger` to narrow this to one key). The reader will tell the user a drag has started.
|
|
201
233
|
- Use the _arrow keys_ while in dragging-mode to change the item's position in the list (down and right are the same, up and left are the same). The reader will tell the user about position changes.
|
|
202
234
|
- Tab to another dnd container while in dragging-mode in order to move the item to it (the item will be moved to it when it gets focus). The reader will tell the user that item was added to the new list.
|
|
203
|
-
- Press
|
|
235
|
+
- Press the drag-trigger key while focused on an item, or the _Escape_ key anywhere to exit dragging mode. The reader will tell the user that they are no longer dragging.
|
|
204
236
|
- Clicking on another item while in drag mode will make it the new drag target. Clicking outside of any draggable will exit dragging-mode (and tell the user)
|
|
205
237
|
- Mouse drag and drop can be preformed independently of keyboard dragging (as in an item can be dragged with the mouse while in or out of keyboard initiated dragging-mode)
|
|
206
238
|
- Keyboard drag uses the same `consider` (only on drag start) and `finalize` (every time the item is moved) events but share only some of the `TRIGGERS`. The same handlers should work fine for both.
|
package/dist/index.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ export interface AriaStrings {
|
|
|
90
90
|
movedToZoneEnd?: (ctx: {itemLabel: string; zoneLabel: string; position: number; count: number}) => string;
|
|
91
91
|
movedToZoneStart?: (ctx: {itemLabel: string; zoneLabel: string; position: number; count: number}) => string;
|
|
92
92
|
dropped?: (ctx: {itemLabel: string; zoneLabel: string; position: number; count: number}) => string;
|
|
93
|
-
zoneActiveInstruction?: string;
|
|
93
|
+
zoneActiveInstruction?: string | ((ctx: {keyboardDragTrigger: "space" | "enter" | "space_or_enter"}) => string);
|
|
94
94
|
zoneDragDisabledInstruction?: string;
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -103,6 +103,15 @@ export interface AriaStrings {
|
|
|
103
103
|
*/
|
|
104
104
|
export declare function setAriaStrings(overrides: AriaStrings | null): void;
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Chooses which key(s) start and stop a keyboard drag on a focused item. Keys outside the trigger are left
|
|
108
|
+
* completely untouched by the library, so the app can use them - ex: "space" keeps Enter free to activate the
|
|
109
|
+
* focused item. This is global and applies to all dndzones. It can be called at any time.
|
|
110
|
+
* Pass null or undefined (or call with no argument) to restore the default, "space_or_enter".
|
|
111
|
+
* @throws {Error} if given anything other than the documented values, null, or undefined
|
|
112
|
+
*/
|
|
113
|
+
export declare function setKeyboardDragTrigger(trigger?: "space" | "enter" | "space_or_enter" | null): void;
|
|
114
|
+
|
|
106
115
|
/**
|
|
107
116
|
* Allows using another key instead of "id" in the items data. This is global and applies to all dndzones.
|
|
108
117
|
* Has to be called when there are no rendered dndzones whatsoever.
|
package/dist/index.js
CHANGED
|
@@ -293,6 +293,24 @@
|
|
|
293
293
|
POINTER: "pointer",
|
|
294
294
|
KEYBOARD: "keyboard"
|
|
295
295
|
};
|
|
296
|
+
|
|
297
|
+
// Maps each valid global keyboardDragTrigger value (see setKeyboardDragTrigger) to the
|
|
298
|
+
// KeyboardEvent.key values it covers. Keys that are not part of the active trigger are left
|
|
299
|
+
// completely untouched by the library.
|
|
300
|
+
var KEYBOARD_DRAG_TRIGGER_KEYS = {
|
|
301
|
+
space: [" "],
|
|
302
|
+
enter: ["Enter"],
|
|
303
|
+
space_or_enter: [" ", "Enter"]
|
|
304
|
+
};
|
|
305
|
+
var DEFAULT_KEYBOARD_DRAG_TRIGGER = "space_or_enter";
|
|
306
|
+
|
|
307
|
+
// Human-readable phrase for each trigger, used by the default screen-reader instruction (see
|
|
308
|
+
// src/helpers/aria.js). Must have exactly the same keys as KEYBOARD_DRAG_TRIGGER_KEYS above.
|
|
309
|
+
var KEYBOARD_DRAG_TRIGGER_PHRASES = {
|
|
310
|
+
space: "space-bar",
|
|
311
|
+
enter: "enter",
|
|
312
|
+
space_or_enter: "space-bar or enter"
|
|
313
|
+
};
|
|
296
314
|
var SHADOW_ITEM_MARKER_PROPERTY_NAME = "isDndShadowItem";
|
|
297
315
|
var SHADOW_ELEMENT_ATTRIBUTE_NAME = "data-is-dnd-shadow-item-internal";
|
|
298
316
|
var SHADOW_ELEMENT_HINT_ATTRIBUTE_NAME = "data-is-dnd-shadow-item-hint";
|
|
@@ -2240,12 +2258,21 @@
|
|
|
2240
2258
|
var itemLabel = _ref5.itemLabel;
|
|
2241
2259
|
return "Stopped dragging item ".concat(itemLabel);
|
|
2242
2260
|
},
|
|
2243
|
-
zoneActiveInstruction:
|
|
2261
|
+
zoneActiveInstruction: function zoneActiveInstruction(_ref6) {
|
|
2262
|
+
var keyboardDragTrigger = _ref6.keyboardDragTrigger;
|
|
2263
|
+
return "Tab to one the items and press ".concat(KEYBOARD_DRAG_TRIGGER_PHRASES[keyboardDragTrigger], " to start dragging it");
|
|
2264
|
+
},
|
|
2244
2265
|
zoneDragDisabledInstruction: "This is a disabled drag and drop list"
|
|
2245
2266
|
};
|
|
2246
2267
|
var FUNCTION_ARIA_STRING_KEYS = ["dragStarted", "movedToPosition", "movedToZoneEnd", "movedToZoneStart", "dropped"];
|
|
2247
|
-
|
|
2268
|
+
// zoneActiveInstruction names the key that starts a drag, so a translation may need to vary it with the trigger.
|
|
2269
|
+
var STRING_OR_FUNCTION_ARIA_STRING_KEYS = ["zoneActiveInstruction"];
|
|
2270
|
+
// zoneDragDisabledInstruction has no drag to start and thus no key to name, so it stays string-only.
|
|
2271
|
+
var STRING_ONLY_ARIA_STRING_KEYS = ["zoneDragDisabledInstruction"];
|
|
2248
2272
|
var ariaStrings = _objectSpread2({}, DEFAULT_ARIA_STRINGS);
|
|
2273
|
+
var instructionCtx = {
|
|
2274
|
+
keyboardDragTrigger: DEFAULT_KEYBOARD_DRAG_TRIGGER
|
|
2275
|
+
};
|
|
2249
2276
|
var ALERT_DIV_ID = "dnd-action-aria-alert";
|
|
2250
2277
|
var alertsDiv;
|
|
2251
2278
|
function initAriaOnBrowser() {
|
|
@@ -2271,11 +2298,11 @@
|
|
|
2271
2298
|
document.body.prepend(alertsDiv);
|
|
2272
2299
|
|
|
2273
2300
|
// setting the instructions
|
|
2274
|
-
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (
|
|
2275
|
-
var
|
|
2276
|
-
id =
|
|
2277
|
-
key =
|
|
2278
|
-
return document.body.prepend(instructionToHiddenDiv(id,
|
|
2301
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (_ref7) {
|
|
2302
|
+
var _ref8 = _slicedToArray(_ref7, 2),
|
|
2303
|
+
id = _ref8[0],
|
|
2304
|
+
key = _ref8[1];
|
|
2305
|
+
return document.body.prepend(instructionToHiddenDiv(id, formatWithFallback(key, instructionCtx)));
|
|
2279
2306
|
});
|
|
2280
2307
|
}
|
|
2281
2308
|
|
|
@@ -2345,7 +2372,10 @@
|
|
|
2345
2372
|
* Pass null to restore the built-in English strings.
|
|
2346
2373
|
* @param {Object | null} overrides - any subset of: dragStarted, movedToPosition, movedToZoneEnd,
|
|
2347
2374
|
* movedToZoneStart, dropped (functions taking a context object and returning a string);
|
|
2348
|
-
* zoneActiveInstruction,
|
|
2375
|
+
* zoneActiveInstruction (a string, or a function taking {keyboardDragTrigger} and returning a string -
|
|
2376
|
+
* useful because a hard-coded translation can't name the right key if the app varies the trigger);
|
|
2377
|
+
* zoneDragDisabledInstruction (a string only - a disabled zone has no drag to start, so there is no key
|
|
2378
|
+
* for a formatter to name)
|
|
2349
2379
|
* @throws {Error} if overrides is not an object or null, contains an unknown key, or contains a value of
|
|
2350
2380
|
* the wrong type. Validation completes before the active strings change.
|
|
2351
2381
|
*/
|
|
@@ -2364,51 +2394,102 @@
|
|
|
2364
2394
|
if (FUNCTION_ARIA_STRING_KEYS.includes(key) && typeof value !== "function") {
|
|
2365
2395
|
throw new Error("".concat(key, " should be a function but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2366
2396
|
}
|
|
2367
|
-
if (
|
|
2397
|
+
if (STRING_OR_FUNCTION_ARIA_STRING_KEYS.includes(key) && typeof value !== "string" && typeof value !== "function") {
|
|
2398
|
+
throw new Error("".concat(key, " should be a string or a function but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2399
|
+
}
|
|
2400
|
+
if (STRING_ONLY_ARIA_STRING_KEYS.includes(key) && typeof value !== "string") {
|
|
2368
2401
|
throw new Error("".concat(key, " should be a string but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2369
2402
|
}
|
|
2370
2403
|
});
|
|
2371
2404
|
ariaStrings = _objectSpread2(_objectSpread2({}, DEFAULT_ARIA_STRINGS), overrides);
|
|
2372
2405
|
}
|
|
2373
|
-
|
|
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
|
-
});
|
|
2406
|
+
refreshInstructions();
|
|
2381
2407
|
}
|
|
2382
2408
|
function formatAriaString(strings, key, ctx) {
|
|
2383
2409
|
var ariaString = strings[key];
|
|
2384
2410
|
return typeof ariaString === "function" ? ariaString(ctx) : ariaString;
|
|
2385
2411
|
}
|
|
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;
|
|
2412
|
+
function formatWithFallback(key, ctx) {
|
|
2396
2413
|
try {
|
|
2397
|
-
|
|
2414
|
+
return formatAriaString(ariaStrings, key, ctx);
|
|
2398
2415
|
} catch (err) {
|
|
2399
2416
|
printDebug(function () {
|
|
2400
2417
|
return ["aria string formatter for \"".concat(key, "\" threw, falling back to the default"), err];
|
|
2401
2418
|
});
|
|
2402
2419
|
try {
|
|
2403
|
-
|
|
2420
|
+
return formatAriaString(DEFAULT_ARIA_STRINGS, key, ctx);
|
|
2404
2421
|
} catch (defaultErr) {
|
|
2405
2422
|
printDebug(function () {
|
|
2406
2423
|
return ["default aria string formatter for \"".concat(key, "\" also threw"), defaultErr];
|
|
2407
2424
|
});
|
|
2408
|
-
|
|
2425
|
+
return "";
|
|
2409
2426
|
}
|
|
2410
2427
|
}
|
|
2411
|
-
|
|
2428
|
+
}
|
|
2429
|
+
function refreshInstructions() {
|
|
2430
|
+
if (isOnServer) return;
|
|
2431
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (_ref9) {
|
|
2432
|
+
var _ref10 = _slicedToArray(_ref9, 2),
|
|
2433
|
+
id = _ref10[0],
|
|
2434
|
+
key = _ref10[1];
|
|
2435
|
+
var div = document.getElementById(id);
|
|
2436
|
+
if (div) renderInstruction(div, formatWithFallback(key, instructionCtx));
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
/**
|
|
2441
|
+
* Sets the context the static instruction formatters receive, and re-renders them. Internal - the
|
|
2442
|
+
* public entry point is setKeyboardDragTrigger.
|
|
2443
|
+
* @param {{keyboardDragTrigger: "space"|"enter"|"space_or_enter"}} ctx
|
|
2444
|
+
*/
|
|
2445
|
+
function setInstructionContext(ctx) {
|
|
2446
|
+
instructionCtx = _objectSpread2({}, ctx);
|
|
2447
|
+
refreshInstructions();
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
/**
|
|
2451
|
+
* Formats and announces one configured ARIA message. If a consumer formatter throws, the built-in
|
|
2452
|
+
* English formatter is used and the error is reported through printDebug. Formatter errors must not
|
|
2453
|
+
* escape because this function runs during the drag lifecycle.
|
|
2454
|
+
* @param {string} key - one of the keys accepted by setAriaStrings
|
|
2455
|
+
* @param {Object} [ctx] - the interpolation context for that key
|
|
2456
|
+
*/
|
|
2457
|
+
function announceToScreenReader(key, ctx) {
|
|
2458
|
+
alertToScreenReader(formatWithFallback(key, ctx));
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
var activeTrigger = DEFAULT_KEYBOARD_DRAG_TRIGGER;
|
|
2462
|
+
|
|
2463
|
+
/**
|
|
2464
|
+
* Chooses which key(s) start and stop a keyboard drag on a focused item. Keys outside the trigger are
|
|
2465
|
+
* left completely untouched by the library (no preventDefault, no stopPropagation), so the app can use
|
|
2466
|
+
* them - ex: "space" keeps Enter free to activate the focused item.
|
|
2467
|
+
* This setting is global to the document and applies to every dndzone. It can be called at any time.
|
|
2468
|
+
* Pass null or undefined (or call with no argument) to restore the default, "space_or_enter".
|
|
2469
|
+
* @param {"space"|"enter"|"space_or_enter"|null|undefined} [trigger]
|
|
2470
|
+
* @throws {Error} if given anything other than the documented values, null, or undefined
|
|
2471
|
+
*/
|
|
2472
|
+
function setKeyboardDragTrigger(trigger) {
|
|
2473
|
+
if (trigger === null || trigger === undefined) {
|
|
2474
|
+
activeTrigger = DEFAULT_KEYBOARD_DRAG_TRIGGER;
|
|
2475
|
+
} else if (typeof trigger !== "string" || !Object.prototype.hasOwnProperty.call(KEYBOARD_DRAG_TRIGGER_KEYS, trigger)) {
|
|
2476
|
+
throw new Error("setKeyboardDragTrigger expects one of ".concat(Object.keys(KEYBOARD_DRAG_TRIGGER_KEYS).map(function (key) {
|
|
2477
|
+
return "\"".concat(key, "\"");
|
|
2478
|
+
}).join(", "), " or null but instead got a ").concat(_typeof(trigger), ", ").concat(toString(trigger)));
|
|
2479
|
+
} else {
|
|
2480
|
+
activeTrigger = trigger;
|
|
2481
|
+
}
|
|
2482
|
+
setInstructionContext({
|
|
2483
|
+
keyboardDragTrigger: activeTrigger
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
/**
|
|
2488
|
+
* @param {string} key - a KeyboardEvent.key value
|
|
2489
|
+
* @return {boolean} whether this key starts/stops a drag under the active trigger
|
|
2490
|
+
*/
|
|
2491
|
+
function isKeyboardDragTriggerKey(key) {
|
|
2492
|
+
return KEYBOARD_DRAG_TRIGGER_KEYS[activeTrigger].includes(key);
|
|
2412
2493
|
}
|
|
2413
2494
|
|
|
2414
2495
|
var DEFAULT_DROP_ZONE_TYPE = "--any--";
|
|
@@ -2659,6 +2740,10 @@
|
|
|
2659
2740
|
case "Enter":
|
|
2660
2741
|
case " ":
|
|
2661
2742
|
{
|
|
2743
|
+
// keys outside the configured trigger belong to the consumer - don't claim them in any way
|
|
2744
|
+
if (!isKeyboardDragTriggerKey(e.key)) {
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2662
2747
|
// we don't want to affect nested input elements or clickable elements
|
|
2663
2748
|
if ((e.target.disabled !== undefined || e.target.href || e.target.isContentEditable) && !allDragTargets.has(e.target)) {
|
|
2664
2749
|
return;
|
|
@@ -3144,7 +3229,7 @@
|
|
|
3144
3229
|
window.addEventListener("touchend", resetStartDrag);
|
|
3145
3230
|
}
|
|
3146
3231
|
function handleKeyDown(e) {
|
|
3147
|
-
if (e.key
|
|
3232
|
+
if (isKeyboardDragTriggerKey(e.key)) isItemsDragDisabled.set(false);
|
|
3148
3233
|
}
|
|
3149
3234
|
function resetStartDrag() {
|
|
3150
3235
|
isItemsDragDisabled.set(true);
|
|
@@ -3195,6 +3280,7 @@
|
|
|
3195
3280
|
exports.setAriaStrings = setAriaStrings;
|
|
3196
3281
|
exports.setDebugMode = setDebugMode;
|
|
3197
3282
|
exports.setFeatureFlag = setFeatureFlag;
|
|
3283
|
+
exports.setKeyboardDragTrigger = setKeyboardDragTrigger;
|
|
3198
3284
|
|
|
3199
3285
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3200
3286
|
|
package/dist/index.mjs
CHANGED
|
@@ -287,6 +287,24 @@ var SOURCES = {
|
|
|
287
287
|
POINTER: "pointer",
|
|
288
288
|
KEYBOARD: "keyboard"
|
|
289
289
|
};
|
|
290
|
+
|
|
291
|
+
// Maps each valid global keyboardDragTrigger value (see setKeyboardDragTrigger) to the
|
|
292
|
+
// KeyboardEvent.key values it covers. Keys that are not part of the active trigger are left
|
|
293
|
+
// completely untouched by the library.
|
|
294
|
+
var KEYBOARD_DRAG_TRIGGER_KEYS = {
|
|
295
|
+
space: [" "],
|
|
296
|
+
enter: ["Enter"],
|
|
297
|
+
space_or_enter: [" ", "Enter"]
|
|
298
|
+
};
|
|
299
|
+
var DEFAULT_KEYBOARD_DRAG_TRIGGER = "space_or_enter";
|
|
300
|
+
|
|
301
|
+
// Human-readable phrase for each trigger, used by the default screen-reader instruction (see
|
|
302
|
+
// src/helpers/aria.js). Must have exactly the same keys as KEYBOARD_DRAG_TRIGGER_KEYS above.
|
|
303
|
+
var KEYBOARD_DRAG_TRIGGER_PHRASES = {
|
|
304
|
+
space: "space-bar",
|
|
305
|
+
enter: "enter",
|
|
306
|
+
space_or_enter: "space-bar or enter"
|
|
307
|
+
};
|
|
290
308
|
var SHADOW_ITEM_MARKER_PROPERTY_NAME = "isDndShadowItem";
|
|
291
309
|
var SHADOW_ELEMENT_ATTRIBUTE_NAME = "data-is-dnd-shadow-item-internal";
|
|
292
310
|
var SHADOW_ELEMENT_HINT_ATTRIBUTE_NAME = "data-is-dnd-shadow-item-hint";
|
|
@@ -2234,12 +2252,21 @@ var DEFAULT_ARIA_STRINGS = {
|
|
|
2234
2252
|
var itemLabel = _ref5.itemLabel;
|
|
2235
2253
|
return "Stopped dragging item ".concat(itemLabel);
|
|
2236
2254
|
},
|
|
2237
|
-
zoneActiveInstruction:
|
|
2255
|
+
zoneActiveInstruction: function zoneActiveInstruction(_ref6) {
|
|
2256
|
+
var keyboardDragTrigger = _ref6.keyboardDragTrigger;
|
|
2257
|
+
return "Tab to one the items and press ".concat(KEYBOARD_DRAG_TRIGGER_PHRASES[keyboardDragTrigger], " to start dragging it");
|
|
2258
|
+
},
|
|
2238
2259
|
zoneDragDisabledInstruction: "This is a disabled drag and drop list"
|
|
2239
2260
|
};
|
|
2240
2261
|
var FUNCTION_ARIA_STRING_KEYS = ["dragStarted", "movedToPosition", "movedToZoneEnd", "movedToZoneStart", "dropped"];
|
|
2241
|
-
|
|
2262
|
+
// zoneActiveInstruction names the key that starts a drag, so a translation may need to vary it with the trigger.
|
|
2263
|
+
var STRING_OR_FUNCTION_ARIA_STRING_KEYS = ["zoneActiveInstruction"];
|
|
2264
|
+
// zoneDragDisabledInstruction has no drag to start and thus no key to name, so it stays string-only.
|
|
2265
|
+
var STRING_ONLY_ARIA_STRING_KEYS = ["zoneDragDisabledInstruction"];
|
|
2242
2266
|
var ariaStrings = _objectSpread2({}, DEFAULT_ARIA_STRINGS);
|
|
2267
|
+
var instructionCtx = {
|
|
2268
|
+
keyboardDragTrigger: DEFAULT_KEYBOARD_DRAG_TRIGGER
|
|
2269
|
+
};
|
|
2243
2270
|
var ALERT_DIV_ID = "dnd-action-aria-alert";
|
|
2244
2271
|
var alertsDiv;
|
|
2245
2272
|
function initAriaOnBrowser() {
|
|
@@ -2265,11 +2292,11 @@ function initAriaOnBrowser() {
|
|
|
2265
2292
|
document.body.prepend(alertsDiv);
|
|
2266
2293
|
|
|
2267
2294
|
// setting the instructions
|
|
2268
|
-
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (
|
|
2269
|
-
var
|
|
2270
|
-
id =
|
|
2271
|
-
key =
|
|
2272
|
-
return document.body.prepend(instructionToHiddenDiv(id,
|
|
2295
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (_ref7) {
|
|
2296
|
+
var _ref8 = _slicedToArray(_ref7, 2),
|
|
2297
|
+
id = _ref8[0],
|
|
2298
|
+
key = _ref8[1];
|
|
2299
|
+
return document.body.prepend(instructionToHiddenDiv(id, formatWithFallback(key, instructionCtx)));
|
|
2273
2300
|
});
|
|
2274
2301
|
}
|
|
2275
2302
|
|
|
@@ -2339,7 +2366,10 @@ function alertToScreenReader(txt) {
|
|
|
2339
2366
|
* Pass null to restore the built-in English strings.
|
|
2340
2367
|
* @param {Object | null} overrides - any subset of: dragStarted, movedToPosition, movedToZoneEnd,
|
|
2341
2368
|
* movedToZoneStart, dropped (functions taking a context object and returning a string);
|
|
2342
|
-
* zoneActiveInstruction,
|
|
2369
|
+
* zoneActiveInstruction (a string, or a function taking {keyboardDragTrigger} and returning a string -
|
|
2370
|
+
* useful because a hard-coded translation can't name the right key if the app varies the trigger);
|
|
2371
|
+
* zoneDragDisabledInstruction (a string only - a disabled zone has no drag to start, so there is no key
|
|
2372
|
+
* for a formatter to name)
|
|
2343
2373
|
* @throws {Error} if overrides is not an object or null, contains an unknown key, or contains a value of
|
|
2344
2374
|
* the wrong type. Validation completes before the active strings change.
|
|
2345
2375
|
*/
|
|
@@ -2358,51 +2388,102 @@ function setAriaStrings(overrides) {
|
|
|
2358
2388
|
if (FUNCTION_ARIA_STRING_KEYS.includes(key) && typeof value !== "function") {
|
|
2359
2389
|
throw new Error("".concat(key, " should be a function but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2360
2390
|
}
|
|
2361
|
-
if (
|
|
2391
|
+
if (STRING_OR_FUNCTION_ARIA_STRING_KEYS.includes(key) && typeof value !== "string" && typeof value !== "function") {
|
|
2392
|
+
throw new Error("".concat(key, " should be a string or a function but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2393
|
+
}
|
|
2394
|
+
if (STRING_ONLY_ARIA_STRING_KEYS.includes(key) && typeof value !== "string") {
|
|
2362
2395
|
throw new Error("".concat(key, " should be a string but instead it is a ").concat(_typeof(value), ", ").concat(toString(value)));
|
|
2363
2396
|
}
|
|
2364
2397
|
});
|
|
2365
2398
|
ariaStrings = _objectSpread2(_objectSpread2({}, DEFAULT_ARIA_STRINGS), overrides);
|
|
2366
2399
|
}
|
|
2367
|
-
|
|
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
|
-
});
|
|
2400
|
+
refreshInstructions();
|
|
2375
2401
|
}
|
|
2376
2402
|
function formatAriaString(strings, key, ctx) {
|
|
2377
2403
|
var ariaString = strings[key];
|
|
2378
2404
|
return typeof ariaString === "function" ? ariaString(ctx) : ariaString;
|
|
2379
2405
|
}
|
|
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;
|
|
2406
|
+
function formatWithFallback(key, ctx) {
|
|
2390
2407
|
try {
|
|
2391
|
-
|
|
2408
|
+
return formatAriaString(ariaStrings, key, ctx);
|
|
2392
2409
|
} catch (err) {
|
|
2393
2410
|
printDebug(function () {
|
|
2394
2411
|
return ["aria string formatter for \"".concat(key, "\" threw, falling back to the default"), err];
|
|
2395
2412
|
});
|
|
2396
2413
|
try {
|
|
2397
|
-
|
|
2414
|
+
return formatAriaString(DEFAULT_ARIA_STRINGS, key, ctx);
|
|
2398
2415
|
} catch (defaultErr) {
|
|
2399
2416
|
printDebug(function () {
|
|
2400
2417
|
return ["default aria string formatter for \"".concat(key, "\" also threw"), defaultErr];
|
|
2401
2418
|
});
|
|
2402
|
-
|
|
2419
|
+
return "";
|
|
2403
2420
|
}
|
|
2404
2421
|
}
|
|
2405
|
-
|
|
2422
|
+
}
|
|
2423
|
+
function refreshInstructions() {
|
|
2424
|
+
if (isOnServer) return;
|
|
2425
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(function (_ref9) {
|
|
2426
|
+
var _ref10 = _slicedToArray(_ref9, 2),
|
|
2427
|
+
id = _ref10[0],
|
|
2428
|
+
key = _ref10[1];
|
|
2429
|
+
var div = document.getElementById(id);
|
|
2430
|
+
if (div) renderInstruction(div, formatWithFallback(key, instructionCtx));
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
/**
|
|
2435
|
+
* Sets the context the static instruction formatters receive, and re-renders them. Internal - the
|
|
2436
|
+
* public entry point is setKeyboardDragTrigger.
|
|
2437
|
+
* @param {{keyboardDragTrigger: "space"|"enter"|"space_or_enter"}} ctx
|
|
2438
|
+
*/
|
|
2439
|
+
function setInstructionContext(ctx) {
|
|
2440
|
+
instructionCtx = _objectSpread2({}, ctx);
|
|
2441
|
+
refreshInstructions();
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
/**
|
|
2445
|
+
* Formats and announces one configured ARIA message. If a consumer formatter throws, the built-in
|
|
2446
|
+
* English formatter is used and the error is reported through printDebug. Formatter errors must not
|
|
2447
|
+
* escape because this function runs during the drag lifecycle.
|
|
2448
|
+
* @param {string} key - one of the keys accepted by setAriaStrings
|
|
2449
|
+
* @param {Object} [ctx] - the interpolation context for that key
|
|
2450
|
+
*/
|
|
2451
|
+
function announceToScreenReader(key, ctx) {
|
|
2452
|
+
alertToScreenReader(formatWithFallback(key, ctx));
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
var activeTrigger = DEFAULT_KEYBOARD_DRAG_TRIGGER;
|
|
2456
|
+
|
|
2457
|
+
/**
|
|
2458
|
+
* Chooses which key(s) start and stop a keyboard drag on a focused item. Keys outside the trigger are
|
|
2459
|
+
* left completely untouched by the library (no preventDefault, no stopPropagation), so the app can use
|
|
2460
|
+
* them - ex: "space" keeps Enter free to activate the focused item.
|
|
2461
|
+
* This setting is global to the document and applies to every dndzone. It can be called at any time.
|
|
2462
|
+
* Pass null or undefined (or call with no argument) to restore the default, "space_or_enter".
|
|
2463
|
+
* @param {"space"|"enter"|"space_or_enter"|null|undefined} [trigger]
|
|
2464
|
+
* @throws {Error} if given anything other than the documented values, null, or undefined
|
|
2465
|
+
*/
|
|
2466
|
+
function setKeyboardDragTrigger(trigger) {
|
|
2467
|
+
if (trigger === null || trigger === undefined) {
|
|
2468
|
+
activeTrigger = DEFAULT_KEYBOARD_DRAG_TRIGGER;
|
|
2469
|
+
} else if (typeof trigger !== "string" || !Object.prototype.hasOwnProperty.call(KEYBOARD_DRAG_TRIGGER_KEYS, trigger)) {
|
|
2470
|
+
throw new Error("setKeyboardDragTrigger expects one of ".concat(Object.keys(KEYBOARD_DRAG_TRIGGER_KEYS).map(function (key) {
|
|
2471
|
+
return "\"".concat(key, "\"");
|
|
2472
|
+
}).join(", "), " or null but instead got a ").concat(_typeof(trigger), ", ").concat(toString(trigger)));
|
|
2473
|
+
} else {
|
|
2474
|
+
activeTrigger = trigger;
|
|
2475
|
+
}
|
|
2476
|
+
setInstructionContext({
|
|
2477
|
+
keyboardDragTrigger: activeTrigger
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
/**
|
|
2482
|
+
* @param {string} key - a KeyboardEvent.key value
|
|
2483
|
+
* @return {boolean} whether this key starts/stops a drag under the active trigger
|
|
2484
|
+
*/
|
|
2485
|
+
function isKeyboardDragTriggerKey(key) {
|
|
2486
|
+
return KEYBOARD_DRAG_TRIGGER_KEYS[activeTrigger].includes(key);
|
|
2406
2487
|
}
|
|
2407
2488
|
|
|
2408
2489
|
var DEFAULT_DROP_ZONE_TYPE = "--any--";
|
|
@@ -2653,6 +2734,10 @@ function dndzone$1(node, options) {
|
|
|
2653
2734
|
case "Enter":
|
|
2654
2735
|
case " ":
|
|
2655
2736
|
{
|
|
2737
|
+
// keys outside the configured trigger belong to the consumer - don't claim them in any way
|
|
2738
|
+
if (!isKeyboardDragTriggerKey(e.key)) {
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2656
2741
|
// we don't want to affect nested input elements or clickable elements
|
|
2657
2742
|
if ((e.target.disabled !== undefined || e.target.href || e.target.isContentEditable) && !allDragTargets.has(e.target)) {
|
|
2658
2743
|
return;
|
|
@@ -3138,7 +3223,7 @@ function dragHandle(handle) {
|
|
|
3138
3223
|
window.addEventListener("touchend", resetStartDrag);
|
|
3139
3224
|
}
|
|
3140
3225
|
function handleKeyDown(e) {
|
|
3141
|
-
if (e.key
|
|
3226
|
+
if (isKeyboardDragTriggerKey(e.key)) isItemsDragDisabled.set(false);
|
|
3142
3227
|
}
|
|
3143
3228
|
function resetStartDrag() {
|
|
3144
3229
|
isItemsDragDisabled.set(true);
|
|
@@ -3175,4 +3260,4 @@ function dragHandle(handle) {
|
|
|
3175
3260
|
};
|
|
3176
3261
|
}
|
|
3177
3262
|
|
|
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 };
|
|
3263
|
+
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, setKeyboardDragTrigger };
|
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.78",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/isaacHagoel/svelte-dnd-action.git"
|
package/src/constants.js
CHANGED
|
@@ -18,6 +18,24 @@ export const SOURCES = {
|
|
|
18
18
|
KEYBOARD: "keyboard"
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
// Maps each valid global keyboardDragTrigger value (see setKeyboardDragTrigger) to the
|
|
22
|
+
// KeyboardEvent.key values it covers. Keys that are not part of the active trigger are left
|
|
23
|
+
// completely untouched by the library.
|
|
24
|
+
export const KEYBOARD_DRAG_TRIGGER_KEYS = {
|
|
25
|
+
space: [" "],
|
|
26
|
+
enter: ["Enter"],
|
|
27
|
+
space_or_enter: [" ", "Enter"]
|
|
28
|
+
};
|
|
29
|
+
export const DEFAULT_KEYBOARD_DRAG_TRIGGER = "space_or_enter";
|
|
30
|
+
|
|
31
|
+
// Human-readable phrase for each trigger, used by the default screen-reader instruction (see
|
|
32
|
+
// src/helpers/aria.js). Must have exactly the same keys as KEYBOARD_DRAG_TRIGGER_KEYS above.
|
|
33
|
+
export const KEYBOARD_DRAG_TRIGGER_PHRASES = {
|
|
34
|
+
space: "space-bar",
|
|
35
|
+
enter: "enter",
|
|
36
|
+
space_or_enter: "space-bar or enter"
|
|
37
|
+
};
|
|
38
|
+
|
|
21
39
|
export const SHADOW_ITEM_MARKER_PROPERTY_NAME = "isDndShadowItem";
|
|
22
40
|
export const SHADOW_ELEMENT_ATTRIBUTE_NAME = "data-is-dnd-shadow-item-internal";
|
|
23
41
|
export const SHADOW_ELEMENT_HINT_ATTRIBUTE_NAME = "data-is-dnd-shadow-item-hint";
|
package/src/helpers/aria.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {isOnServer, printDebug} from "../constants";
|
|
1
|
+
import {DEFAULT_KEYBOARD_DRAG_TRIGGER, KEYBOARD_DRAG_TRIGGER_PHRASES, isOnServer, printDebug} from "../constants";
|
|
2
2
|
import {toString} from "./util";
|
|
3
3
|
|
|
4
4
|
const INSTRUCTION_IDs = {
|
|
@@ -18,14 +18,19 @@ const DEFAULT_ARIA_STRINGS = {
|
|
|
18
18
|
movedToZoneEnd: ({itemLabel, zoneLabel}) => `Moved item ${itemLabel} to the end of the list ${zoneLabel}`,
|
|
19
19
|
movedToZoneStart: ({itemLabel, zoneLabel}) => `Moved item ${itemLabel} to the beginning of the list ${zoneLabel}`,
|
|
20
20
|
dropped: ({itemLabel}) => `Stopped dragging item ${itemLabel}`,
|
|
21
|
-
zoneActiveInstruction:
|
|
21
|
+
zoneActiveInstruction: ({keyboardDragTrigger}) =>
|
|
22
|
+
`Tab to one the items and press ${KEYBOARD_DRAG_TRIGGER_PHRASES[keyboardDragTrigger]} to start dragging it`,
|
|
22
23
|
zoneDragDisabledInstruction: "This is a disabled drag and drop list"
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
const FUNCTION_ARIA_STRING_KEYS = ["dragStarted", "movedToPosition", "movedToZoneEnd", "movedToZoneStart", "dropped"];
|
|
26
|
-
|
|
27
|
+
// zoneActiveInstruction names the key that starts a drag, so a translation may need to vary it with the trigger.
|
|
28
|
+
const STRING_OR_FUNCTION_ARIA_STRING_KEYS = ["zoneActiveInstruction"];
|
|
29
|
+
// zoneDragDisabledInstruction has no drag to start and thus no key to name, so it stays string-only.
|
|
30
|
+
const STRING_ONLY_ARIA_STRING_KEYS = ["zoneDragDisabledInstruction"];
|
|
27
31
|
|
|
28
32
|
let ariaStrings = {...DEFAULT_ARIA_STRINGS};
|
|
33
|
+
let instructionCtx = {keyboardDragTrigger: DEFAULT_KEYBOARD_DRAG_TRIGGER};
|
|
29
34
|
|
|
30
35
|
const ALERT_DIV_ID = "dnd-action-aria-alert";
|
|
31
36
|
let alertsDiv;
|
|
@@ -53,7 +58,9 @@ function initAriaOnBrowser() {
|
|
|
53
58
|
document.body.prepend(alertsDiv);
|
|
54
59
|
|
|
55
60
|
// setting the instructions
|
|
56
|
-
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(([id, key]) =>
|
|
61
|
+
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(([id, key]) =>
|
|
62
|
+
document.body.prepend(instructionToHiddenDiv(id, formatWithFallback(key, instructionCtx)))
|
|
63
|
+
);
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
/**
|
|
@@ -121,7 +128,10 @@ export function alertToScreenReader(txt) {
|
|
|
121
128
|
* Pass null to restore the built-in English strings.
|
|
122
129
|
* @param {Object | null} overrides - any subset of: dragStarted, movedToPosition, movedToZoneEnd,
|
|
123
130
|
* movedToZoneStart, dropped (functions taking a context object and returning a string);
|
|
124
|
-
* zoneActiveInstruction,
|
|
131
|
+
* zoneActiveInstruction (a string, or a function taking {keyboardDragTrigger} and returning a string -
|
|
132
|
+
* useful because a hard-coded translation can't name the right key if the app varies the trigger);
|
|
133
|
+
* zoneDragDisabledInstruction (a string only - a disabled zone has no drag to start, so there is no key
|
|
134
|
+
* for a formatter to name)
|
|
125
135
|
* @throws {Error} if overrides is not an object or null, contains an unknown key, or contains a value of
|
|
126
136
|
* the wrong type. Validation completes before the active strings change.
|
|
127
137
|
*/
|
|
@@ -140,22 +150,53 @@ export function setAriaStrings(overrides) {
|
|
|
140
150
|
if (FUNCTION_ARIA_STRING_KEYS.includes(key) && typeof value !== "function") {
|
|
141
151
|
throw new Error(`${key} should be a function but instead it is a ${typeof value}, ${toString(value)}`);
|
|
142
152
|
}
|
|
143
|
-
if (
|
|
153
|
+
if (STRING_OR_FUNCTION_ARIA_STRING_KEYS.includes(key) && typeof value !== "string" && typeof value !== "function") {
|
|
154
|
+
throw new Error(`${key} should be a string or a function but instead it is a ${typeof value}, ${toString(value)}`);
|
|
155
|
+
}
|
|
156
|
+
if (STRING_ONLY_ARIA_STRING_KEYS.includes(key) && typeof value !== "string") {
|
|
144
157
|
throw new Error(`${key} should be a string but instead it is a ${typeof value}, ${toString(value)}`);
|
|
145
158
|
}
|
|
146
159
|
});
|
|
147
160
|
ariaStrings = {...DEFAULT_ARIA_STRINGS, ...overrides};
|
|
148
161
|
}
|
|
162
|
+
refreshInstructions();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function formatAriaString(strings, key, ctx) {
|
|
166
|
+
const ariaString = strings[key];
|
|
167
|
+
return typeof ariaString === "function" ? ariaString(ctx) : ariaString;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function formatWithFallback(key, ctx) {
|
|
171
|
+
try {
|
|
172
|
+
return formatAriaString(ariaStrings, key, ctx);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
printDebug(() => [`aria string formatter for "${key}" threw, falling back to the default`, err]);
|
|
175
|
+
try {
|
|
176
|
+
return formatAriaString(DEFAULT_ARIA_STRINGS, key, ctx);
|
|
177
|
+
} catch (defaultErr) {
|
|
178
|
+
printDebug(() => [`default aria string formatter for "${key}" also threw`, defaultErr]);
|
|
179
|
+
return "";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function refreshInstructions() {
|
|
149
185
|
if (isOnServer) return;
|
|
150
186
|
Object.entries(INSTRUCTION_ID_TO_STRING_KEY).forEach(([id, key]) => {
|
|
151
187
|
const div = document.getElementById(id);
|
|
152
|
-
if (div) renderInstruction(div,
|
|
188
|
+
if (div) renderInstruction(div, formatWithFallback(key, instructionCtx));
|
|
153
189
|
});
|
|
154
190
|
}
|
|
155
191
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Sets the context the static instruction formatters receive, and re-renders them. Internal - the
|
|
194
|
+
* public entry point is setKeyboardDragTrigger.
|
|
195
|
+
* @param {{keyboardDragTrigger: "space"|"enter"|"space_or_enter"}} ctx
|
|
196
|
+
*/
|
|
197
|
+
export function setInstructionContext(ctx) {
|
|
198
|
+
instructionCtx = {...ctx};
|
|
199
|
+
refreshInstructions();
|
|
159
200
|
}
|
|
160
201
|
|
|
161
202
|
/**
|
|
@@ -166,17 +207,5 @@ function formatAriaString(strings, key, ctx) {
|
|
|
166
207
|
* @param {Object} [ctx] - the interpolation context for that key
|
|
167
208
|
*/
|
|
168
209
|
export function announceToScreenReader(key, ctx) {
|
|
169
|
-
|
|
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);
|
|
210
|
+
alertToScreenReader(formatWithFallback(key, ctx));
|
|
182
211
|
}
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export {dndzone} from "./action.js";
|
|
2
2
|
export {dragHandleZone, dragHandle} from "./wrappers/withDragHandles";
|
|
3
3
|
export {alertToScreenReader, setAriaStrings} from "./helpers/aria";
|
|
4
|
+
export {setKeyboardDragTrigger} from "./keyboardDragTrigger";
|
|
4
5
|
export {
|
|
5
6
|
TRIGGERS,
|
|
6
7
|
SOURCES,
|
package/src/keyboardAction.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {decrementActiveDropZoneCount, incrementActiveDropZoneCount, ITEM_ID_KEY, SOURCES, TRIGGERS} from "./constants";
|
|
2
|
+
import {isKeyboardDragTriggerKey} from "./keyboardDragTrigger";
|
|
2
3
|
import {styleActiveDropZones, styleInactiveDropZones} from "./helpers/styler";
|
|
3
4
|
import {dispatchConsiderEvent, dispatchFinalizeEvent} from "./helpers/dispatcher";
|
|
4
5
|
import {initAria, announceToScreenReader, destroyAria} from "./helpers/aria";
|
|
@@ -230,6 +231,10 @@ export function dndzone(node, options) {
|
|
|
230
231
|
switch (e.key) {
|
|
231
232
|
case "Enter":
|
|
232
233
|
case " ": {
|
|
234
|
+
// keys outside the configured trigger belong to the consumer - don't claim them in any way
|
|
235
|
+
if (!isKeyboardDragTriggerKey(e.key)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
233
238
|
// we don't want to affect nested input elements or clickable elements
|
|
234
239
|
if ((e.target.disabled !== undefined || e.target.href || e.target.isContentEditable) && !allDragTargets.has(e.target)) {
|
|
235
240
|
return;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {DEFAULT_KEYBOARD_DRAG_TRIGGER, KEYBOARD_DRAG_TRIGGER_KEYS} from "./constants";
|
|
2
|
+
import {toString} from "./helpers/util";
|
|
3
|
+
import {setInstructionContext} from "./helpers/aria";
|
|
4
|
+
|
|
5
|
+
let activeTrigger = DEFAULT_KEYBOARD_DRAG_TRIGGER;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Chooses which key(s) start and stop a keyboard drag on a focused item. Keys outside the trigger are
|
|
9
|
+
* left completely untouched by the library (no preventDefault, no stopPropagation), so the app can use
|
|
10
|
+
* them - ex: "space" keeps Enter free to activate the focused item.
|
|
11
|
+
* This setting is global to the document and applies to every dndzone. It can be called at any time.
|
|
12
|
+
* Pass null or undefined (or call with no argument) to restore the default, "space_or_enter".
|
|
13
|
+
* @param {"space"|"enter"|"space_or_enter"|null|undefined} [trigger]
|
|
14
|
+
* @throws {Error} if given anything other than the documented values, null, or undefined
|
|
15
|
+
*/
|
|
16
|
+
export function setKeyboardDragTrigger(trigger) {
|
|
17
|
+
if (trigger === null || trigger === undefined) {
|
|
18
|
+
activeTrigger = DEFAULT_KEYBOARD_DRAG_TRIGGER;
|
|
19
|
+
} else if (typeof trigger !== "string" || !Object.prototype.hasOwnProperty.call(KEYBOARD_DRAG_TRIGGER_KEYS, trigger)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`setKeyboardDragTrigger expects one of ${Object.keys(KEYBOARD_DRAG_TRIGGER_KEYS)
|
|
22
|
+
.map(key => `"${key}"`)
|
|
23
|
+
.join(", ")} or null but instead got a ${typeof trigger}, ${toString(trigger)}`
|
|
24
|
+
);
|
|
25
|
+
} else {
|
|
26
|
+
activeTrigger = trigger;
|
|
27
|
+
}
|
|
28
|
+
setInstructionContext({keyboardDragTrigger: activeTrigger});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @return {"space"|"enter"|"space_or_enter"} the active trigger
|
|
33
|
+
*/
|
|
34
|
+
export function getKeyboardDragTrigger() {
|
|
35
|
+
return activeTrigger;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} key - a KeyboardEvent.key value
|
|
40
|
+
* @return {boolean} whether this key starts/stops a drag under the active trigger
|
|
41
|
+
*/
|
|
42
|
+
export function isKeyboardDragTriggerKey(key) {
|
|
43
|
+
return KEYBOARD_DRAG_TRIGGER_KEYS[activeTrigger].includes(key);
|
|
44
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {SOURCES, TRIGGERS} from "../constants";
|
|
2
|
+
import {isKeyboardDragTriggerKey} from "../keyboardDragTrigger";
|
|
2
3
|
import {dndzone} from "../action";
|
|
3
4
|
import {createStore} from "./simpleStore";
|
|
4
5
|
|
|
@@ -104,7 +105,7 @@ export function dragHandle(handle) {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
function handleKeyDown(e) {
|
|
107
|
-
if (e.key
|
|
108
|
+
if (isKeyboardDragTriggerKey(e.key)) isItemsDragDisabled.set(false);
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
function resetStartDrag() {
|