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 CHANGED
@@ -1,7 +1,5 @@
1
1
  # SVELTE DND ACTION [![Known Vulnerabilities](https://snyk.io/test/github/isaacHagoel/svelte-dnd-action/badge.svg?targetFile=package.json)](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: `Tabulez jusqu'à un élément et appuyez sur espace ou entrée pour le déplacer`,
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 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.
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 _Space_/_Enter_ 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.
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: "Tab to one the items and press space-bar or enter to start dragging it",
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
- var STRING_ARIA_STRING_KEYS = ["zoneActiveInstruction", "zoneDragDisabledInstruction"];
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 (_ref6) {
2275
- var _ref7 = _slicedToArray(_ref6, 2),
2276
- id = _ref7[0],
2277
- key = _ref7[1];
2278
- return document.body.prepend(instructionToHiddenDiv(id, ariaStrings[key]));
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, zoneDragDisabledInstruction (strings)
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 (STRING_ARIA_STRING_KEYS.includes(key) && typeof value !== "string") {
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
- 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
- });
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
- text = formatAriaString(ariaStrings, key, ctx);
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
- text = formatAriaString(DEFAULT_ARIA_STRINGS, key, ctx);
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
- text = "";
2425
+ return "";
2409
2426
  }
2410
2427
  }
2411
- alertToScreenReader(text);
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 === "Enter" || e.key === " ") isItemsDragDisabled.set(false);
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: "Tab to one the items and press space-bar or enter to start dragging it",
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
- var STRING_ARIA_STRING_KEYS = ["zoneActiveInstruction", "zoneDragDisabledInstruction"];
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 (_ref6) {
2269
- var _ref7 = _slicedToArray(_ref6, 2),
2270
- id = _ref7[0],
2271
- key = _ref7[1];
2272
- return document.body.prepend(instructionToHiddenDiv(id, ariaStrings[key]));
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, zoneDragDisabledInstruction (strings)
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 (STRING_ARIA_STRING_KEYS.includes(key) && typeof value !== "string") {
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
- 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
- });
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
- text = formatAriaString(ariaStrings, key, ctx);
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
- text = formatAriaString(DEFAULT_ARIA_STRINGS, key, ctx);
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
- text = "";
2419
+ return "";
2403
2420
  }
2404
2421
  }
2405
- alertToScreenReader(text);
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 === "Enter" || e.key === " ") isItemsDragDisabled.set(false);
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.77",
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";
@@ -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: "Tab to one the items and press space-bar or enter to start dragging it",
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
- const STRING_ARIA_STRING_KEYS = ["zoneActiveInstruction", "zoneDragDisabledInstruction"];
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]) => document.body.prepend(instructionToHiddenDiv(id, ariaStrings[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, zoneDragDisabledInstruction (strings)
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 (STRING_ARIA_STRING_KEYS.includes(key) && typeof value !== "string") {
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, ariaStrings[key]);
188
+ if (div) renderInstruction(div, formatWithFallback(key, instructionCtx));
153
189
  });
154
190
  }
155
191
 
156
- function formatAriaString(strings, key, ctx) {
157
- const ariaString = strings[key];
158
- return typeof ariaString === "function" ? ariaString(ctx) : ariaString;
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
- 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);
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,
@@ -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 === "Enter" || e.key === " ") isItemsDragDisabled.set(false);
108
+ if (isKeyboardDragTriggerKey(e.key)) isItemsDragDisabled.set(false);
108
109
  }
109
110
 
110
111
  function resetStartDrag() {