@baleada/logic 0.24.9 → 0.24.11

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/lib/index.cjs CHANGED
@@ -434,7 +434,14 @@ class Recognizeable {
434
434
  effects: {}
435
435
  };
436
436
  this.maxSequenceLength = options?.maxSequenceLength || defaultOptions.maxSequenceLength;
437
- this.effects = options?.effects || defaultOptions.effects;
437
+ this.effects = options.effects || defaultOptions.effects;
438
+ const stops = {};
439
+ for (const effect in this.effects) {
440
+ const effectOrConfig = this.effects[effect];
441
+ stops[effect] = isEffectConfig(effectOrConfig) ? effectOrConfig.stop : () => {
442
+ };
443
+ }
444
+ this.computedStops = stops;
438
445
  this.resetComputedMetadata();
439
446
  this.setSequence(sequence);
440
447
  this.effectApi = {
@@ -467,6 +474,10 @@ class Recognizeable {
467
474
  set sequence(sequence) {
468
475
  this.setSequence(sequence);
469
476
  }
477
+ computedStops;
478
+ get stops() {
479
+ return this.computedStops;
480
+ }
470
481
  get status() {
471
482
  return this.computedStatus;
472
483
  }
@@ -482,13 +493,11 @@ class Recognizeable {
482
493
  this.recognizing();
483
494
  const type = this.toType(sequenceItem), pushSequence = (sequenceItem2) => {
484
495
  newSequence.push(sequenceItem2);
485
- if (this.maxSequenceLength !== true && newSequence.length > this.maxSequenceLength) {
496
+ if (this.maxSequenceLength !== true && newSequence.length > this.maxSequenceLength)
486
497
  newSequence.shift();
487
- }
488
498
  }, newSequence = [];
489
- for (const sequenceItem2 of this.sequence) {
490
- pushSequence(sequenceItem2);
491
- }
499
+ for (const previousSequenceItem of this.sequence)
500
+ pushSequence(previousSequenceItem);
492
501
  pushSequence(sequenceItem);
493
502
  this.effectApi.getSequence = () => newSequence;
494
503
  this.effectApi.pushSequence = pushSequence;
@@ -497,7 +506,14 @@ class Recognizeable {
497
506
  }),
498
507
  optionsByType: options.listenInjection?.optionsByType || {}
499
508
  };
500
- this.effects[type]?.(sequenceItem, { ...this.effectApi });
509
+ switch (typeof this.effects[type]) {
510
+ case "function":
511
+ this.effects[type](sequenceItem, { ...this.effectApi });
512
+ break;
513
+ case "object":
514
+ this.effects[type].effect(sequenceItem, { ...this.effectApi });
515
+ break;
516
+ }
501
517
  switch (this.status) {
502
518
  case "ready":
503
519
  case "denied":
@@ -536,6 +552,9 @@ class Recognizeable {
536
552
  }
537
553
  }
538
554
  }
555
+ function isEffectConfig(effectOrConfig) {
556
+ return typeof effectOrConfig === "object";
557
+ }
539
558
 
540
559
  class Listenable {
541
560
  computedRecognizeable;
@@ -659,7 +678,11 @@ class Listenable {
659
678
  for (const type of this.recognizeableEffectsKeys) {
660
679
  const listenable = new Listenable(type);
661
680
  listenable.listen(guardedEffect, options);
662
- this.active.add({ id: listenable });
681
+ this.active.add({
682
+ id: listenable,
683
+ target: "target" in options ? options.target : void 0,
684
+ stopExtension: this.recognizeable.stops[type]
685
+ });
663
686
  }
664
687
  }
665
688
  documentEventListen(effect, options) {
@@ -690,14 +713,15 @@ class Listenable {
690
713
  case void 0:
691
714
  break;
692
715
  default:
693
- const stoppables = [...this.active].filter((active) => !target || ("target" in active ? active.target === target : false)), shouldUpdateStatus = stoppables.length === this.active.size;
716
+ const stoppables = createFilter(
717
+ (active) => !target || ("target" in active ? active.target === target : false)
718
+ )([...this.active]), shouldUpdateStatus = stoppables.length === this.active.size;
694
719
  for (const stoppable of stoppables) {
695
720
  stop(stoppable);
696
721
  this.active.delete(stoppable);
697
722
  }
698
- if (shouldUpdateStatus) {
723
+ if (shouldUpdateStatus)
699
724
  this.stopped();
700
- }
701
725
  break;
702
726
  }
703
727
  return this;
@@ -746,7 +770,9 @@ class Listenable {
746
770
  }
747
771
  function stop(stoppable) {
748
772
  if (stoppable.id instanceof Listenable) {
749
- stoppable.id.stop();
773
+ const { id: id2, target: target2, stopExtension } = stoppable;
774
+ id2.stop();
775
+ stopExtension(target2);
750
776
  return;
751
777
  }
752
778
  if (lazyCollections.some((type) => observerAssertionsByType[type](stoppable.id))(["intersect", "mutate", "resize"])) {
@@ -854,7 +880,7 @@ function createKeypress(keycomboOrKeycombos, options = {}) {
854
880
  matchPredicatesByKeycombo,
855
881
  getDownCombos,
856
882
  predicateValid,
857
- cleanup,
883
+ stop,
858
884
  statuses,
859
885
  toStatus,
860
886
  setStatus,
@@ -898,7 +924,7 @@ function createKeypress(keycomboOrKeycombos, options = {}) {
898
924
  const { getMetadata } = api, metadata = getMetadata();
899
925
  metadata.keycombo = downCombos[0];
900
926
  localStatus = "recognizing";
901
- cleanup();
927
+ stop();
902
928
  storeKeyboardTimeMetadata({
903
929
  event,
904
930
  api,
@@ -943,14 +969,14 @@ function createKeypress(keycomboOrKeycombos, options = {}) {
943
969
  return;
944
970
  }
945
971
  denied();
946
- cleanup();
972
+ stop();
947
973
  onUp?.(toHookApi(api));
948
974
  };
949
975
  const visibilitychange = (event, api) => {
950
976
  if (document.visibilityState === "hidden") {
951
977
  clearStatuses();
952
978
  localStatus = "recognizing";
953
- cleanup();
979
+ stop();
954
980
  }
955
981
  onVisibilitychange?.(toHookApi(api));
956
982
  };
@@ -996,7 +1022,7 @@ function createKeyrelease(keycomboOrKeycombos, options = {}) {
996
1022
  matchPredicatesByKeycombo,
997
1023
  getDownCombos,
998
1024
  predicateValid,
999
- cleanup,
1025
+ stop,
1000
1026
  statuses,
1001
1027
  toStatus,
1002
1028
  setStatus,
@@ -1038,7 +1064,7 @@ function createKeyrelease(keycomboOrKeycombos, options = {}) {
1038
1064
  event.preventDefault();
1039
1065
  const { getMetadata } = api;
1040
1066
  localStatus = "recognizing";
1041
- cleanup();
1067
+ stop();
1042
1068
  storeKeyboardTimeMetadata({
1043
1069
  event,
1044
1070
  api,
@@ -1094,7 +1120,7 @@ function createKeyrelease(keycomboOrKeycombos, options = {}) {
1094
1120
  if (document.visibilityState === "hidden") {
1095
1121
  clearStatuses();
1096
1122
  localStatus = "recognizing";
1097
- cleanup();
1123
+ stop();
1098
1124
  }
1099
1125
  onVisibilitychange?.(toHookApi(api));
1100
1126
  };
@@ -1186,7 +1212,7 @@ function createKeychord(keycombos, options = {}) {
1186
1212
  if (preventsDefaultUnlessDenied)
1187
1213
  event.preventDefault();
1188
1214
  localStatuses[playedIndex] = "recognizing";
1189
- keyStates[playedIndex].cleanup();
1215
+ keyStates[playedIndex].stop();
1190
1216
  storeKeyboardTimeMetadata({
1191
1217
  event,
1192
1218
  api,
@@ -1269,7 +1295,7 @@ function createKeychord(keycombos, options = {}) {
1269
1295
  for (const { clearStatuses } of keyStates)
1270
1296
  clearStatuses();
1271
1297
  localStatuses[playedIndex] = "recognizing";
1272
- keyStates[playedIndex].cleanup();
1298
+ keyStates[playedIndex].stop();
1273
1299
  playedIndex = 0;
1274
1300
  }
1275
1301
  onVisibilitychange?.(toHookApi(api));
@@ -1327,7 +1353,7 @@ function createMousepress(options = {}) {
1327
1353
  onLeave,
1328
1354
  onMove,
1329
1355
  onUp
1330
- } = { ...defaultOptions$h, ...options }, cleanup = (target) => {
1356
+ } = { ...defaultOptions$h, ...options }, stop = (target) => {
1331
1357
  window.cancelAnimationFrame(request);
1332
1358
  target.removeEventListener("mousemove", mousemoveEffect);
1333
1359
  };
@@ -1368,7 +1394,7 @@ function createMousepress(options = {}) {
1368
1394
  const { denied, listenInjection: { optionsByType: { mouseleave: { target } } } } = api;
1369
1395
  if (mouseStatus === "down") {
1370
1396
  denied();
1371
- cleanup(target);
1397
+ stop(target);
1372
1398
  mouseStatus = "leave";
1373
1399
  }
1374
1400
  onLeave?.(toHookApi(api));
@@ -1378,12 +1404,15 @@ function createMousepress(options = {}) {
1378
1404
  if (mouseStatus !== "down")
1379
1405
  return;
1380
1406
  denied();
1381
- cleanup(target);
1407
+ stop(target);
1382
1408
  mouseStatus = "up";
1383
1409
  onUp?.(toHookApi(api));
1384
1410
  };
1385
1411
  return {
1386
- mousedown,
1412
+ mousedown: {
1413
+ effect: mousedown,
1414
+ stop
1415
+ },
1387
1416
  mouseleave,
1388
1417
  mouseup
1389
1418
  };
@@ -1418,7 +1447,7 @@ function createMouserelease(options = {}) {
1418
1447
  onLeave,
1419
1448
  onMove,
1420
1449
  onUp
1421
- } = { ...defaultOptions$g, ...options }, cleanup = (target) => {
1450
+ } = { ...defaultOptions$g, ...options }, stop = (target) => {
1422
1451
  window.cancelAnimationFrame(request);
1423
1452
  target.removeEventListener("mousemove", mousemoveEffect);
1424
1453
  };
@@ -1448,7 +1477,7 @@ function createMouserelease(options = {}) {
1448
1477
  const { denied, listenInjection: { optionsByType: { mouseleave: { target } } } } = api;
1449
1478
  if (mouseStatus === "down") {
1450
1479
  denied();
1451
- cleanup(target);
1480
+ stop(target);
1452
1481
  mouseStatus = "leave";
1453
1482
  }
1454
1483
  onLeave?.(toHookApi(api));
@@ -1458,7 +1487,7 @@ function createMouserelease(options = {}) {
1458
1487
  return;
1459
1488
  storePointerMoveMetadata(event, api);
1460
1489
  const { listenInjection: { optionsByType: { mouseup: { target } } } } = api;
1461
- cleanup(target);
1490
+ stop(target);
1462
1491
  mouseStatus = "up";
1463
1492
  recognize(event, api);
1464
1493
  onUp?.(toHookApi(api));
@@ -1472,7 +1501,10 @@ function createMouserelease(options = {}) {
1472
1501
  }
1473
1502
  };
1474
1503
  return {
1475
- mousedown,
1504
+ mousedown: {
1505
+ effect: mousedown,
1506
+ stop
1507
+ },
1476
1508
  mouseleave,
1477
1509
  mouseup
1478
1510
  };
@@ -1505,7 +1537,7 @@ function createTouchpress(options = {}) {
1505
1537
  onCancel,
1506
1538
  onMove,
1507
1539
  onEnd
1508
- } = { ...defaultOptions$f, ...options }, cleanup = () => {
1540
+ } = { ...defaultOptions$f, ...options }, stop = () => {
1509
1541
  window.cancelAnimationFrame(request);
1510
1542
  };
1511
1543
  let request;
@@ -1514,7 +1546,7 @@ function createTouchpress(options = {}) {
1514
1546
  const { denied } = api;
1515
1547
  totalTouches++;
1516
1548
  if (totalTouches > 1) {
1517
- cleanup();
1549
+ stop();
1518
1550
  denied();
1519
1551
  onStart?.(toHookApi(api));
1520
1552
  return;
@@ -1547,14 +1579,14 @@ function createTouchpress(options = {}) {
1547
1579
  };
1548
1580
  const touchcancel = (event, api) => {
1549
1581
  const { denied } = api;
1550
- cleanup();
1582
+ stop();
1551
1583
  denied();
1552
1584
  totalTouches--;
1553
1585
  onCancel?.(toHookApi(api));
1554
1586
  };
1555
1587
  const touchend = (event, api) => {
1556
1588
  const { denied } = api;
1557
- cleanup();
1589
+ stop();
1558
1590
  denied();
1559
1591
  totalTouches--;
1560
1592
  onEnd?.(toHookApi(api));
@@ -1596,7 +1628,7 @@ function createTouchrelease(options = {}) {
1596
1628
  onCancel,
1597
1629
  onMove,
1598
1630
  onEnd
1599
- } = { ...defaultOptions$e, ...options }, cleanup = () => {
1631
+ } = { ...defaultOptions$e, ...options }, stop = () => {
1600
1632
  window.cancelAnimationFrame(request);
1601
1633
  };
1602
1634
  let request;
@@ -1605,7 +1637,7 @@ function createTouchrelease(options = {}) {
1605
1637
  const { denied } = api;
1606
1638
  totalTouches++;
1607
1639
  if (totalTouches > 1) {
1608
- cleanup();
1640
+ stop();
1609
1641
  denied();
1610
1642
  onStart?.(toHookApi(api));
1611
1643
  return;
@@ -1628,7 +1660,7 @@ function createTouchrelease(options = {}) {
1628
1660
  };
1629
1661
  const touchcancel = (event, api) => {
1630
1662
  const { denied } = api;
1631
- cleanup();
1663
+ stop();
1632
1664
  denied();
1633
1665
  totalTouches--;
1634
1666
  onCancel?.(toHookApi(api));
@@ -1636,13 +1668,13 @@ function createTouchrelease(options = {}) {
1636
1668
  const touchend = (event, api) => {
1637
1669
  const { denied } = api;
1638
1670
  if (totalTouches !== 1) {
1639
- cleanup();
1671
+ stop();
1640
1672
  denied();
1641
1673
  onEnd?.(toHookApi(api));
1642
1674
  return;
1643
1675
  }
1644
1676
  storePointerMoveMetadata(event, api);
1645
- cleanup();
1677
+ stop();
1646
1678
  totalTouches--;
1647
1679
  recognize(event, api);
1648
1680
  onEnd?.(toHookApi(api));
@@ -2180,26 +2212,61 @@ const defaultOptions$d = {
2180
2212
  };
2181
2213
  function createFocusable(order, options = {}) {
2182
2214
  const { predicatesElement, tabbableSelector } = { ...defaultOptions$d, ...options }, predicateFocusable = (element) => element.matches(tabbableSelector);
2183
- return (element) => {
2184
- if (predicatesElement && predicateFocusable(element))
2185
- return element;
2186
- switch (order) {
2187
- case "first":
2215
+ switch (order) {
2216
+ case "first":
2217
+ return (element) => {
2218
+ if (predicatesElement && predicateFocusable(element))
2219
+ return element;
2188
2220
  for (let i = 0; i < element.children.length; i++) {
2189
2221
  const focusable = createFocusable(order, { predicatesElement: true })(element.children[i]);
2190
2222
  if (focusable)
2191
2223
  return focusable;
2192
2224
  }
2193
- break;
2194
- case "last":
2225
+ };
2226
+ case "last":
2227
+ return (element) => {
2228
+ if (predicatesElement && predicateFocusable(element))
2229
+ return element;
2195
2230
  for (let i = element.children.length - 1; i > -1; i--) {
2196
2231
  const focusable = createFocusable(order, { predicatesElement: true })(element.children[i]);
2197
2232
  if (focusable)
2198
2233
  return focusable;
2199
2234
  }
2200
- break;
2201
- }
2202
- };
2235
+ };
2236
+ case "next":
2237
+ return (element) => {
2238
+ if (predicatesElement && predicateFocusable(element))
2239
+ return element;
2240
+ const focusable = createFocusable("first")(element);
2241
+ if (focusable)
2242
+ return focusable;
2243
+ let current = element;
2244
+ while (current && current !== document.documentElement) {
2245
+ const nextSibling = current.nextElementSibling;
2246
+ if (nextSibling) {
2247
+ const focusable2 = createFocusable("first", { predicatesElement: true })(nextSibling);
2248
+ if (focusable2)
2249
+ return focusable2;
2250
+ }
2251
+ current = current.parentElement;
2252
+ }
2253
+ };
2254
+ case "previous":
2255
+ return (element) => {
2256
+ if (predicatesElement && predicateFocusable(element))
2257
+ return element;
2258
+ let current = element;
2259
+ while (current && current !== document.documentElement) {
2260
+ const previousSibling = current.previousElementSibling;
2261
+ if (previousSibling) {
2262
+ const focusable = createFocusable("last", { predicatesElement: true })(previousSibling);
2263
+ if (focusable)
2264
+ return focusable;
2265
+ }
2266
+ current = current.parentElement;
2267
+ }
2268
+ };
2269
+ }
2203
2270
  }
2204
2271
  function createComputedStyle(pseudoElement) {
2205
2272
  return (element) => getComputedStyle(element, pseudoElement);
@@ -2626,7 +2693,7 @@ function createKeyState({
2626
2693
  return lazyCollections.some(
2627
2694
  (validAlias) => lazyCollections.includes(validAlias)(aliases)
2628
2695
  )(validAliases);
2629
- }, cleanup = () => {
2696
+ }, stop = () => {
2630
2697
  window.cancelAnimationFrame(getRequest());
2631
2698
  }, statuses = [], toStatus = (...params) => createValue(...params)(statuses), setStatus = (...params) => createSet(...params)(statuses), clearStatuses = (...params) => createClear(...params)(statuses), deleteStatus = (...params) => createDelete(...params)(statuses);
2632
2699
  return {
@@ -2638,7 +2705,7 @@ function createKeyState({
2638
2705
  validAliases,
2639
2706
  getDownCombos,
2640
2707
  predicateValid,
2641
- cleanup,
2708
+ stop,
2642
2709
  statuses,
2643
2710
  toStatus,
2644
2711
  setStatus,
@@ -2764,6 +2831,11 @@ function storeKeyboardTimeMetadata({
2764
2831
  effect(event);
2765
2832
  }
2766
2833
  }, storeDuration = () => {
2834
+ const sequence = api.getSequence();
2835
+ if (!document.body.contains(
2836
+ lazyCollections.at(-1)(sequence).target
2837
+ ))
2838
+ return;
2767
2839
  const request = requestAnimationFrame((timestamp) => {
2768
2840
  if (!getShouldStore())
2769
2841
  return;
@@ -2883,6 +2955,11 @@ function storePointerTimeMetadata(event, api, getShouldStore, setRequest, recogn
2883
2955
  if (getStatus() === "recognized")
2884
2956
  effect(event2);
2885
2957
  }, storeDuration = () => {
2958
+ const sequence = api.getSequence();
2959
+ if (!document.body.contains(
2960
+ lazyCollections.at(-1)(sequence).target
2961
+ ))
2962
+ return;
2886
2963
  const request = requestAnimationFrame((timestamp) => {
2887
2964
  if (!getShouldStore())
2888
2965
  return;
package/lib/index.d.ts CHANGED
@@ -253,9 +253,13 @@ type GraphAsyncEdge<Id extends string, StateValue> = {
253
253
  type RecognizeableOptions<Type extends ListenableSupportedType, Metadata extends Record<any, any>> = {
254
254
  maxSequenceLength?: true | number;
255
255
  effects?: {
256
- [type in Type]?: RecognizeableEffect<type, Metadata>;
256
+ [type in Type]?: RecognizeableEffect<type, Metadata> | RecognizeableEffectConfig<type, Metadata>;
257
257
  };
258
258
  };
259
+ type RecognizeableEffectConfig<Type extends ListenableSupportedType, Metadata extends Record<any, any>> = {
260
+ effect: RecognizeableEffect<Type, Metadata>;
261
+ stop: (target: RecognizeableStopTarget<Type>) => void;
262
+ };
259
263
  type RecognizeableEffect<Type extends ListenableSupportedType, Metadata extends Record<any, any>> = (sequenceItem: ListenEffectParam<Type>, api: RecognizeableEffectApi<Type, Metadata>) => void;
260
264
  type RecognizeableEffectApi<Type extends ListenableSupportedType, Metadata extends Record<any, any>> = {
261
265
  getStatus: () => RecognizeableStatus;
@@ -274,6 +278,10 @@ type RecognizeOptions<Type extends ListenableSupportedType> = {
274
278
  optionsByType: Record<Type, ListenOptions<Type>>;
275
279
  };
276
280
  };
281
+ type RecognizeableStops<Type extends ListenableSupportedType> = {
282
+ [type in Type]?: (target: RecognizeableStopTarget<type>) => void;
283
+ };
284
+ type RecognizeableStopTarget<Type extends ListenableSupportedType> = ('target' extends keyof ListenOptions<Type> ? ListenOptions<Type>['target'] : never);
277
285
  /**
278
286
  * [Docs](https://baleada.dev/docs/logic/classes/recognizeable)
279
287
  */
@@ -290,6 +298,8 @@ declare class Recognizeable<Type extends ListenableSupportedType, Metadata exten
290
298
  private ready;
291
299
  get sequence(): ListenEffectParam<Type>[];
292
300
  set sequence(sequence: ListenEffectParam<Type>[]);
301
+ private computedStops;
302
+ get stops(): RecognizeableStops<Type>;
293
303
  get status(): RecognizeableStatus;
294
304
  get metadata(): Metadata;
295
305
  private computedSequence;
@@ -359,6 +369,8 @@ type ListenableActive<Type extends ListenableSupportedType, RecognizeableMetadat
359
369
  id: ListenableActiveEventId<Type>;
360
370
  } : {
361
371
  id: Listenable<Type, RecognizeableMetadata>;
372
+ target: RecognizeableStopTarget<Type>;
373
+ stopExtension: (target: RecognizeableStopTarget<Type>) => void;
362
374
  };
363
375
  type ListenableActiveEventId<Type extends ListenableSupportedEventType> = [
364
376
  type: Type,
@@ -626,7 +638,7 @@ type CreateFocusableOptions = {
626
638
  /**
627
639
  * [Docs](https://baleada.dev/docs/logic/pipes/focusable)
628
640
  */
629
- declare function createFocusable(order: 'first' | 'last', options?: CreateFocusableOptions): ElementTransform<HTMLElement, HTMLElement | undefined>;
641
+ declare function createFocusable(order: 'first' | 'last' | 'next' | 'previous', options?: CreateFocusableOptions): ElementTransform<HTMLElement, HTMLElement | undefined>;
630
642
  /**
631
643
  * [Docs](https://baleada.dev/docs/logic/pipes/computed-style)
632
644
  */
@@ -1524,7 +1536,10 @@ type MousepressHookApi = HookApi<MousepressType, MousepressMetadata>;
1524
1536
  * [Docs](https://baleada.dev/docs/logic/factories/mousepress)
1525
1537
  */
1526
1538
  declare function createMousepress(options?: MousepressOptions): {
1527
- mousedown: RecognizeableEffect<"mousedown", MousepressMetadata>;
1539
+ mousedown: {
1540
+ effect: RecognizeableEffect<"mousedown", MousepressMetadata>;
1541
+ stop: (target: RecognizeableStopTarget<MousepressType>) => void;
1542
+ };
1528
1543
  mouseleave: RecognizeableEffect<"mouseleave", MousepressMetadata>;
1529
1544
  mouseup: RecognizeableEffect<"mouseup", MousepressMetadata>;
1530
1545
  };
@@ -1550,7 +1565,10 @@ type MousereleaseHookApi = HookApi<MousereleaseType, MousereleaseMetadata>;
1550
1565
  * [Docs](https://baleada.dev/docs/logic/factories/mouserelease)
1551
1566
  */
1552
1567
  declare function createMouserelease(options?: MousereleaseOptions): {
1553
- mousedown: RecognizeableEffect<"mousedown", MousereleaseMetadata>;
1568
+ mousedown: {
1569
+ effect: RecognizeableEffect<"mousedown", MousereleaseMetadata>;
1570
+ stop: (target: RecognizeableStopTarget<MousereleaseType>) => void;
1571
+ };
1554
1572
  mouseleave: RecognizeableEffect<"mouseleave", MousereleaseMetadata>;
1555
1573
  mouseup: RecognizeableEffect<"mouseup", MousereleaseMetadata>;
1556
1574
  };
@@ -1606,4 +1624,4 @@ declare class Touchrelease extends Listenable<TouchreleaseType, TouchreleaseMeta
1606
1624
  get metadata(): TouchreleaseMetadata;
1607
1625
  }
1608
1626
 
1609
- export { AnimateFrame, AnimateFrameEffect, AnimateOptions, Animateable, AnimateableKeyframe, AnimateableOptions, AnimateableStatus, Broadcastable, BroadcastableOptions, BroadcastableStatus, ColorInterpolationMethod, Compareable, CompareableOptions, CompareableStatus, CompleteOptions, Completeable, CompleteableOptions, CompleteableStatus, Copyable, CopyableOptions, CopyableStatus, CreateStepsOptions$1 as CreateDirectedAcyclicStepsOptions, CreateFocusableOptions, CreateGraphOptions, CreateKeycomboMatchOptions, CreateMixOptions, CreatePathConfig, CreateResultsOptions, Delayable, DelayableEffect, DelayableOptions, DelayableStatus, Drawable, DrawableOptions, DrawableStatus, DrawableStroke, Fetchable, FetchableOptions, FetchableStatus, Fullscreenable, FullscreenableGetElement, FullscreenableOptions, FullscreenableStatus, Grantable, GrantableOptions, GrantableStatus, Keychord, KeychordHook, KeychordHookApi, KeychordMetadata, KeychordOptions, KeychordType, Keypress, KeypressHook, KeypressHookApi, KeypressMetadata, KeypressOptions, KeypressType, Keyrelease, KeyreleaseHook, KeyreleaseHookApi, KeyreleaseMetadata, KeyreleaseOptions, KeyreleaseType, Konami, KonamiHook, KonamiHookApi, KonamiMetadata, KonamiOptions, KonamiType, ListenEffect, ListenEffectParam, ListenOptions, Listenable, ListenableActive, ListenableKeycombo, ListenableMousecombo, ListenableOptions, ListenablePointercombo, ListenableStatus, ListenableSupportedEventType, ListenableSupportedType, MixColor, Mousepress, MousepressHook, MousepressHookApi, MousepressMetadata, MousepressOptions, MousepressType, Mouserelease, MousereleaseHook, MousereleaseHookApi, MousereleaseMetadata, MousereleaseOptions, MousereleaseType, Navigateable, NavigateableOptions, NavigateableStatus, PickOptions, Pickable, PickableOptions, PickableStatus, Potentiality, RecognizeOptions, Recognizeable, RecognizeableEffect, RecognizeableOptions, RecognizeableStatus, Resolveable, ResolveableOptions, ResolveableStatus, Shareable, ShareableOptions, ShareableStatus, Storeable, StoreableOptions, StoreableStatus, ToGraphYielded, Touchpress, TouchpressHook, TouchpressHookApi, TouchpressMetadata, TouchpressOptions, TouchpressType, Touchrelease, TouchreleaseHook, TouchreleaseHookApi, TouchreleaseMetadata, TouchreleaseOptions, TouchreleaseType, createClear$1 as createAssociativeArrayClear, createDelete$1 as createAssociativeArrayDelete, createHas$1 as createAssociativeArrayHas, createKeys$1 as createAssociativeArrayKeys, createSet$1 as createAssociativeArraySet, createValue$1 as createAssociativeArrayValue, createValues as createAssociativeArrayValues, createBreadthPathConfig, createChildren, createClamp, createClear, createClip, createClone, createComputedStyle, createConcat, createAncestor$1 as createDecisionTreeAncestor, createCommonAncestors$1 as createDecisionTreeCommonAncestors, createNodeDepthFirstSteps$1 as createDecisionTreeNodeDepthFirstSteps, createPath$1 as createDecisionTreePath, createDepthFirstSteps$1 as createDecisionTreeSteps, createTree$1 as createDecisionTreeTree, createDeepEqual, createDeepMerge, createDelete, createDepthPathConfig, createDetermine, createAncestor$2 as createDirectedAcyclicAncestor, createAncestor as createDirectedAcyclicAsyncAncestor, createCommonAncestors as createDirectedAcyclicAsyncCommonAncestors, createDepthFirstSteps as createDirectedAcyclicAsyncDepthFirstSteps, createLayers as createDirectedAcyclicAsyncLayers, createNodeDepthFirstSteps as createDirectedAcyclicAsyncNodeDepthFirstSteps, createPath as createDirectedAcyclicAsyncPath, createTree as createDirectedAcyclicAsyncTree, createCommonAncestors$2 as createDirectedAcyclicCommonAncestors, createDepthFirstSteps$2 as createDirectedAcyclicDepthFirstSteps, createLayers$1 as createDirectedAcyclicLayers, createNodeDepthFirstSteps$2 as createDirectedAcyclicNodeDepthFirstSteps, createPath$2 as createDirectedAcyclicPath, createRoots as createDirectedAcyclicRoots, createTree$2 as createDirectedAcyclicTree, createEntries, createEqual, createEvery, createFilter, createFilterAsync, createFindAsync, createFindIndexAsync, createFocusable, createForEachAsync, createGraph, createGreater, createGreaterOrEqual, createHas, createIncoming, createIndegree, createInsert, createKeychord, createKeycomboMatch, createKeypress, createKeyrelease, createKeys, createKonami, createLess, createLessOrEqual, createList, createMap, createMapAsync, createMix, createMousepress, createMouserelease, createNumber, createOmit, createOnlyChild, createOutdegree, createOutgoing, createPick, createReduce, createReduceAsync, createRemove, createReorder, createReplace, createResults, createReverse, createRoot, createSanitize, createSet, createShuffle, createSiblings, createSlice, createSlug, createSome, createSort, createSwap, createTerminal, createTotalSiblings, createTouchpress, createTouchrelease, createFind as createTreeFind, createUnique, createValue, easingsNetInBack, easingsNetInCirc, easingsNetInCubic, easingsNetInExpo, easingsNetInOutBack, easingsNetInOutCirc, easingsNetInOutCubic, easingsNetInOutExpo, easingsNetInOutQuad, easingsNetInOutQuint, easingsNetInOutSine, easingsNetInQuad, easingsNetInQuart, easingsNetInQuint, easingsNetInSine, easingsNetOutBack, easingsNetOutCirc, easingsNetOutCubic, easingsNetOutExpo, easingsNetOutQuad, easingsNetOutQuint, easingsNetOutSine, linear, materialAccelerated, materialDecelerated, materialStandard, toD, toFlattenedD, toMessageListenParams, verouEase, verouEaseIn, verouEaseInOut, verouEaseOut };
1627
+ export { AnimateFrame, AnimateFrameEffect, AnimateOptions, Animateable, AnimateableKeyframe, AnimateableOptions, AnimateableStatus, Broadcastable, BroadcastableOptions, BroadcastableStatus, ColorInterpolationMethod, Compareable, CompareableOptions, CompareableStatus, CompleteOptions, Completeable, CompleteableOptions, CompleteableStatus, Copyable, CopyableOptions, CopyableStatus, CreateStepsOptions$1 as CreateDirectedAcyclicStepsOptions, CreateFocusableOptions, CreateGraphOptions, CreateKeycomboMatchOptions, CreateMixOptions, CreatePathConfig, CreateResultsOptions, Delayable, DelayableEffect, DelayableOptions, DelayableStatus, Drawable, DrawableOptions, DrawableStatus, DrawableStroke, Fetchable, FetchableOptions, FetchableStatus, Fullscreenable, FullscreenableGetElement, FullscreenableOptions, FullscreenableStatus, Grantable, GrantableOptions, GrantableStatus, Keychord, KeychordHook, KeychordHookApi, KeychordMetadata, KeychordOptions, KeychordType, Keypress, KeypressHook, KeypressHookApi, KeypressMetadata, KeypressOptions, KeypressType, Keyrelease, KeyreleaseHook, KeyreleaseHookApi, KeyreleaseMetadata, KeyreleaseOptions, KeyreleaseType, Konami, KonamiHook, KonamiHookApi, KonamiMetadata, KonamiOptions, KonamiType, ListenEffect, ListenEffectParam, ListenOptions, Listenable, ListenableActive, ListenableKeycombo, ListenableMousecombo, ListenableOptions, ListenablePointercombo, ListenableStatus, ListenableSupportedEventType, ListenableSupportedType, MixColor, Mousepress, MousepressHook, MousepressHookApi, MousepressMetadata, MousepressOptions, MousepressType, Mouserelease, MousereleaseHook, MousereleaseHookApi, MousereleaseMetadata, MousereleaseOptions, MousereleaseType, Navigateable, NavigateableOptions, NavigateableStatus, PickOptions, Pickable, PickableOptions, PickableStatus, Potentiality, RecognizeOptions, Recognizeable, RecognizeableEffect, RecognizeableEffectConfig, RecognizeableOptions, RecognizeableStatus, RecognizeableStopTarget, Resolveable, ResolveableOptions, ResolveableStatus, Shareable, ShareableOptions, ShareableStatus, Storeable, StoreableOptions, StoreableStatus, ToGraphYielded, Touchpress, TouchpressHook, TouchpressHookApi, TouchpressMetadata, TouchpressOptions, TouchpressType, Touchrelease, TouchreleaseHook, TouchreleaseHookApi, TouchreleaseMetadata, TouchreleaseOptions, TouchreleaseType, createClear$1 as createAssociativeArrayClear, createDelete$1 as createAssociativeArrayDelete, createHas$1 as createAssociativeArrayHas, createKeys$1 as createAssociativeArrayKeys, createSet$1 as createAssociativeArraySet, createValue$1 as createAssociativeArrayValue, createValues as createAssociativeArrayValues, createBreadthPathConfig, createChildren, createClamp, createClear, createClip, createClone, createComputedStyle, createConcat, createAncestor$1 as createDecisionTreeAncestor, createCommonAncestors$1 as createDecisionTreeCommonAncestors, createNodeDepthFirstSteps$1 as createDecisionTreeNodeDepthFirstSteps, createPath$1 as createDecisionTreePath, createDepthFirstSteps$1 as createDecisionTreeSteps, createTree$1 as createDecisionTreeTree, createDeepEqual, createDeepMerge, createDelete, createDepthPathConfig, createDetermine, createAncestor$2 as createDirectedAcyclicAncestor, createAncestor as createDirectedAcyclicAsyncAncestor, createCommonAncestors as createDirectedAcyclicAsyncCommonAncestors, createDepthFirstSteps as createDirectedAcyclicAsyncDepthFirstSteps, createLayers as createDirectedAcyclicAsyncLayers, createNodeDepthFirstSteps as createDirectedAcyclicAsyncNodeDepthFirstSteps, createPath as createDirectedAcyclicAsyncPath, createTree as createDirectedAcyclicAsyncTree, createCommonAncestors$2 as createDirectedAcyclicCommonAncestors, createDepthFirstSteps$2 as createDirectedAcyclicDepthFirstSteps, createLayers$1 as createDirectedAcyclicLayers, createNodeDepthFirstSteps$2 as createDirectedAcyclicNodeDepthFirstSteps, createPath$2 as createDirectedAcyclicPath, createRoots as createDirectedAcyclicRoots, createTree$2 as createDirectedAcyclicTree, createEntries, createEqual, createEvery, createFilter, createFilterAsync, createFindAsync, createFindIndexAsync, createFocusable, createForEachAsync, createGraph, createGreater, createGreaterOrEqual, createHas, createIncoming, createIndegree, createInsert, createKeychord, createKeycomboMatch, createKeypress, createKeyrelease, createKeys, createKonami, createLess, createLessOrEqual, createList, createMap, createMapAsync, createMix, createMousepress, createMouserelease, createNumber, createOmit, createOnlyChild, createOutdegree, createOutgoing, createPick, createReduce, createReduceAsync, createRemove, createReorder, createReplace, createResults, createReverse, createRoot, createSanitize, createSet, createShuffle, createSiblings, createSlice, createSlug, createSome, createSort, createSwap, createTerminal, createTotalSiblings, createTouchpress, createTouchrelease, createFind as createTreeFind, createUnique, createValue, easingsNetInBack, easingsNetInCirc, easingsNetInCubic, easingsNetInExpo, easingsNetInOutBack, easingsNetInOutCirc, easingsNetInOutCubic, easingsNetInOutExpo, easingsNetInOutQuad, easingsNetInOutQuint, easingsNetInOutSine, easingsNetInQuad, easingsNetInQuart, easingsNetInQuint, easingsNetInSine, easingsNetOutBack, easingsNetOutCirc, easingsNetOutCubic, easingsNetOutExpo, easingsNetOutQuad, easingsNetOutQuint, easingsNetOutSine, linear, materialAccelerated, materialDecelerated, materialStandard, toD, toFlattenedD, toMessageListenParams, verouEase, verouEaseIn, verouEaseInOut, verouEaseOut };
package/lib/index.js CHANGED
@@ -432,7 +432,14 @@ class Recognizeable {
432
432
  effects: {}
433
433
  };
434
434
  this.maxSequenceLength = options?.maxSequenceLength || defaultOptions.maxSequenceLength;
435
- this.effects = options?.effects || defaultOptions.effects;
435
+ this.effects = options.effects || defaultOptions.effects;
436
+ const stops = {};
437
+ for (const effect in this.effects) {
438
+ const effectOrConfig = this.effects[effect];
439
+ stops[effect] = isEffectConfig(effectOrConfig) ? effectOrConfig.stop : () => {
440
+ };
441
+ }
442
+ this.computedStops = stops;
436
443
  this.resetComputedMetadata();
437
444
  this.setSequence(sequence);
438
445
  this.effectApi = {
@@ -465,6 +472,10 @@ class Recognizeable {
465
472
  set sequence(sequence) {
466
473
  this.setSequence(sequence);
467
474
  }
475
+ computedStops;
476
+ get stops() {
477
+ return this.computedStops;
478
+ }
468
479
  get status() {
469
480
  return this.computedStatus;
470
481
  }
@@ -480,13 +491,11 @@ class Recognizeable {
480
491
  this.recognizing();
481
492
  const type = this.toType(sequenceItem), pushSequence = (sequenceItem2) => {
482
493
  newSequence.push(sequenceItem2);
483
- if (this.maxSequenceLength !== true && newSequence.length > this.maxSequenceLength) {
494
+ if (this.maxSequenceLength !== true && newSequence.length > this.maxSequenceLength)
484
495
  newSequence.shift();
485
- }
486
496
  }, newSequence = [];
487
- for (const sequenceItem2 of this.sequence) {
488
- pushSequence(sequenceItem2);
489
- }
497
+ for (const previousSequenceItem of this.sequence)
498
+ pushSequence(previousSequenceItem);
490
499
  pushSequence(sequenceItem);
491
500
  this.effectApi.getSequence = () => newSequence;
492
501
  this.effectApi.pushSequence = pushSequence;
@@ -495,7 +504,14 @@ class Recognizeable {
495
504
  }),
496
505
  optionsByType: options.listenInjection?.optionsByType || {}
497
506
  };
498
- this.effects[type]?.(sequenceItem, { ...this.effectApi });
507
+ switch (typeof this.effects[type]) {
508
+ case "function":
509
+ this.effects[type](sequenceItem, { ...this.effectApi });
510
+ break;
511
+ case "object":
512
+ this.effects[type].effect(sequenceItem, { ...this.effectApi });
513
+ break;
514
+ }
499
515
  switch (this.status) {
500
516
  case "ready":
501
517
  case "denied":
@@ -534,6 +550,9 @@ class Recognizeable {
534
550
  }
535
551
  }
536
552
  }
553
+ function isEffectConfig(effectOrConfig) {
554
+ return typeof effectOrConfig === "object";
555
+ }
537
556
 
538
557
  class Listenable {
539
558
  computedRecognizeable;
@@ -657,7 +676,11 @@ class Listenable {
657
676
  for (const type of this.recognizeableEffectsKeys) {
658
677
  const listenable = new Listenable(type);
659
678
  listenable.listen(guardedEffect, options);
660
- this.active.add({ id: listenable });
679
+ this.active.add({
680
+ id: listenable,
681
+ target: "target" in options ? options.target : void 0,
682
+ stopExtension: this.recognizeable.stops[type]
683
+ });
661
684
  }
662
685
  }
663
686
  documentEventListen(effect, options) {
@@ -688,14 +711,15 @@ class Listenable {
688
711
  case void 0:
689
712
  break;
690
713
  default:
691
- const stoppables = [...this.active].filter((active) => !target || ("target" in active ? active.target === target : false)), shouldUpdateStatus = stoppables.length === this.active.size;
714
+ const stoppables = createFilter(
715
+ (active) => !target || ("target" in active ? active.target === target : false)
716
+ )([...this.active]), shouldUpdateStatus = stoppables.length === this.active.size;
692
717
  for (const stoppable of stoppables) {
693
718
  stop(stoppable);
694
719
  this.active.delete(stoppable);
695
720
  }
696
- if (shouldUpdateStatus) {
721
+ if (shouldUpdateStatus)
697
722
  this.stopped();
698
- }
699
723
  break;
700
724
  }
701
725
  return this;
@@ -744,7 +768,9 @@ class Listenable {
744
768
  }
745
769
  function stop(stoppable) {
746
770
  if (stoppable.id instanceof Listenable) {
747
- stoppable.id.stop();
771
+ const { id: id2, target: target2, stopExtension } = stoppable;
772
+ id2.stop();
773
+ stopExtension(target2);
748
774
  return;
749
775
  }
750
776
  if (some((type) => observerAssertionsByType[type](stoppable.id))(["intersect", "mutate", "resize"])) {
@@ -852,7 +878,7 @@ function createKeypress(keycomboOrKeycombos, options = {}) {
852
878
  matchPredicatesByKeycombo,
853
879
  getDownCombos,
854
880
  predicateValid,
855
- cleanup,
881
+ stop,
856
882
  statuses,
857
883
  toStatus,
858
884
  setStatus,
@@ -896,7 +922,7 @@ function createKeypress(keycomboOrKeycombos, options = {}) {
896
922
  const { getMetadata } = api, metadata = getMetadata();
897
923
  metadata.keycombo = downCombos[0];
898
924
  localStatus = "recognizing";
899
- cleanup();
925
+ stop();
900
926
  storeKeyboardTimeMetadata({
901
927
  event,
902
928
  api,
@@ -941,14 +967,14 @@ function createKeypress(keycomboOrKeycombos, options = {}) {
941
967
  return;
942
968
  }
943
969
  denied();
944
- cleanup();
970
+ stop();
945
971
  onUp?.(toHookApi(api));
946
972
  };
947
973
  const visibilitychange = (event, api) => {
948
974
  if (document.visibilityState === "hidden") {
949
975
  clearStatuses();
950
976
  localStatus = "recognizing";
951
- cleanup();
977
+ stop();
952
978
  }
953
979
  onVisibilitychange?.(toHookApi(api));
954
980
  };
@@ -994,7 +1020,7 @@ function createKeyrelease(keycomboOrKeycombos, options = {}) {
994
1020
  matchPredicatesByKeycombo,
995
1021
  getDownCombos,
996
1022
  predicateValid,
997
- cleanup,
1023
+ stop,
998
1024
  statuses,
999
1025
  toStatus,
1000
1026
  setStatus,
@@ -1036,7 +1062,7 @@ function createKeyrelease(keycomboOrKeycombos, options = {}) {
1036
1062
  event.preventDefault();
1037
1063
  const { getMetadata } = api;
1038
1064
  localStatus = "recognizing";
1039
- cleanup();
1065
+ stop();
1040
1066
  storeKeyboardTimeMetadata({
1041
1067
  event,
1042
1068
  api,
@@ -1092,7 +1118,7 @@ function createKeyrelease(keycomboOrKeycombos, options = {}) {
1092
1118
  if (document.visibilityState === "hidden") {
1093
1119
  clearStatuses();
1094
1120
  localStatus = "recognizing";
1095
- cleanup();
1121
+ stop();
1096
1122
  }
1097
1123
  onVisibilitychange?.(toHookApi(api));
1098
1124
  };
@@ -1184,7 +1210,7 @@ function createKeychord(keycombos, options = {}) {
1184
1210
  if (preventsDefaultUnlessDenied)
1185
1211
  event.preventDefault();
1186
1212
  localStatuses[playedIndex] = "recognizing";
1187
- keyStates[playedIndex].cleanup();
1213
+ keyStates[playedIndex].stop();
1188
1214
  storeKeyboardTimeMetadata({
1189
1215
  event,
1190
1216
  api,
@@ -1267,7 +1293,7 @@ function createKeychord(keycombos, options = {}) {
1267
1293
  for (const { clearStatuses } of keyStates)
1268
1294
  clearStatuses();
1269
1295
  localStatuses[playedIndex] = "recognizing";
1270
- keyStates[playedIndex].cleanup();
1296
+ keyStates[playedIndex].stop();
1271
1297
  playedIndex = 0;
1272
1298
  }
1273
1299
  onVisibilitychange?.(toHookApi(api));
@@ -1325,7 +1351,7 @@ function createMousepress(options = {}) {
1325
1351
  onLeave,
1326
1352
  onMove,
1327
1353
  onUp
1328
- } = { ...defaultOptions$h, ...options }, cleanup = (target) => {
1354
+ } = { ...defaultOptions$h, ...options }, stop = (target) => {
1329
1355
  window.cancelAnimationFrame(request);
1330
1356
  target.removeEventListener("mousemove", mousemoveEffect);
1331
1357
  };
@@ -1366,7 +1392,7 @@ function createMousepress(options = {}) {
1366
1392
  const { denied, listenInjection: { optionsByType: { mouseleave: { target } } } } = api;
1367
1393
  if (mouseStatus === "down") {
1368
1394
  denied();
1369
- cleanup(target);
1395
+ stop(target);
1370
1396
  mouseStatus = "leave";
1371
1397
  }
1372
1398
  onLeave?.(toHookApi(api));
@@ -1376,12 +1402,15 @@ function createMousepress(options = {}) {
1376
1402
  if (mouseStatus !== "down")
1377
1403
  return;
1378
1404
  denied();
1379
- cleanup(target);
1405
+ stop(target);
1380
1406
  mouseStatus = "up";
1381
1407
  onUp?.(toHookApi(api));
1382
1408
  };
1383
1409
  return {
1384
- mousedown,
1410
+ mousedown: {
1411
+ effect: mousedown,
1412
+ stop
1413
+ },
1385
1414
  mouseleave,
1386
1415
  mouseup
1387
1416
  };
@@ -1416,7 +1445,7 @@ function createMouserelease(options = {}) {
1416
1445
  onLeave,
1417
1446
  onMove,
1418
1447
  onUp
1419
- } = { ...defaultOptions$g, ...options }, cleanup = (target) => {
1448
+ } = { ...defaultOptions$g, ...options }, stop = (target) => {
1420
1449
  window.cancelAnimationFrame(request);
1421
1450
  target.removeEventListener("mousemove", mousemoveEffect);
1422
1451
  };
@@ -1446,7 +1475,7 @@ function createMouserelease(options = {}) {
1446
1475
  const { denied, listenInjection: { optionsByType: { mouseleave: { target } } } } = api;
1447
1476
  if (mouseStatus === "down") {
1448
1477
  denied();
1449
- cleanup(target);
1478
+ stop(target);
1450
1479
  mouseStatus = "leave";
1451
1480
  }
1452
1481
  onLeave?.(toHookApi(api));
@@ -1456,7 +1485,7 @@ function createMouserelease(options = {}) {
1456
1485
  return;
1457
1486
  storePointerMoveMetadata(event, api);
1458
1487
  const { listenInjection: { optionsByType: { mouseup: { target } } } } = api;
1459
- cleanup(target);
1488
+ stop(target);
1460
1489
  mouseStatus = "up";
1461
1490
  recognize(event, api);
1462
1491
  onUp?.(toHookApi(api));
@@ -1470,7 +1499,10 @@ function createMouserelease(options = {}) {
1470
1499
  }
1471
1500
  };
1472
1501
  return {
1473
- mousedown,
1502
+ mousedown: {
1503
+ effect: mousedown,
1504
+ stop
1505
+ },
1474
1506
  mouseleave,
1475
1507
  mouseup
1476
1508
  };
@@ -1503,7 +1535,7 @@ function createTouchpress(options = {}) {
1503
1535
  onCancel,
1504
1536
  onMove,
1505
1537
  onEnd
1506
- } = { ...defaultOptions$f, ...options }, cleanup = () => {
1538
+ } = { ...defaultOptions$f, ...options }, stop = () => {
1507
1539
  window.cancelAnimationFrame(request);
1508
1540
  };
1509
1541
  let request;
@@ -1512,7 +1544,7 @@ function createTouchpress(options = {}) {
1512
1544
  const { denied } = api;
1513
1545
  totalTouches++;
1514
1546
  if (totalTouches > 1) {
1515
- cleanup();
1547
+ stop();
1516
1548
  denied();
1517
1549
  onStart?.(toHookApi(api));
1518
1550
  return;
@@ -1545,14 +1577,14 @@ function createTouchpress(options = {}) {
1545
1577
  };
1546
1578
  const touchcancel = (event, api) => {
1547
1579
  const { denied } = api;
1548
- cleanup();
1580
+ stop();
1549
1581
  denied();
1550
1582
  totalTouches--;
1551
1583
  onCancel?.(toHookApi(api));
1552
1584
  };
1553
1585
  const touchend = (event, api) => {
1554
1586
  const { denied } = api;
1555
- cleanup();
1587
+ stop();
1556
1588
  denied();
1557
1589
  totalTouches--;
1558
1590
  onEnd?.(toHookApi(api));
@@ -1594,7 +1626,7 @@ function createTouchrelease(options = {}) {
1594
1626
  onCancel,
1595
1627
  onMove,
1596
1628
  onEnd
1597
- } = { ...defaultOptions$e, ...options }, cleanup = () => {
1629
+ } = { ...defaultOptions$e, ...options }, stop = () => {
1598
1630
  window.cancelAnimationFrame(request);
1599
1631
  };
1600
1632
  let request;
@@ -1603,7 +1635,7 @@ function createTouchrelease(options = {}) {
1603
1635
  const { denied } = api;
1604
1636
  totalTouches++;
1605
1637
  if (totalTouches > 1) {
1606
- cleanup();
1638
+ stop();
1607
1639
  denied();
1608
1640
  onStart?.(toHookApi(api));
1609
1641
  return;
@@ -1626,7 +1658,7 @@ function createTouchrelease(options = {}) {
1626
1658
  };
1627
1659
  const touchcancel = (event, api) => {
1628
1660
  const { denied } = api;
1629
- cleanup();
1661
+ stop();
1630
1662
  denied();
1631
1663
  totalTouches--;
1632
1664
  onCancel?.(toHookApi(api));
@@ -1634,13 +1666,13 @@ function createTouchrelease(options = {}) {
1634
1666
  const touchend = (event, api) => {
1635
1667
  const { denied } = api;
1636
1668
  if (totalTouches !== 1) {
1637
- cleanup();
1669
+ stop();
1638
1670
  denied();
1639
1671
  onEnd?.(toHookApi(api));
1640
1672
  return;
1641
1673
  }
1642
1674
  storePointerMoveMetadata(event, api);
1643
- cleanup();
1675
+ stop();
1644
1676
  totalTouches--;
1645
1677
  recognize(event, api);
1646
1678
  onEnd?.(toHookApi(api));
@@ -2178,26 +2210,61 @@ const defaultOptions$d = {
2178
2210
  };
2179
2211
  function createFocusable(order, options = {}) {
2180
2212
  const { predicatesElement, tabbableSelector } = { ...defaultOptions$d, ...options }, predicateFocusable = (element) => element.matches(tabbableSelector);
2181
- return (element) => {
2182
- if (predicatesElement && predicateFocusable(element))
2183
- return element;
2184
- switch (order) {
2185
- case "first":
2213
+ switch (order) {
2214
+ case "first":
2215
+ return (element) => {
2216
+ if (predicatesElement && predicateFocusable(element))
2217
+ return element;
2186
2218
  for (let i = 0; i < element.children.length; i++) {
2187
2219
  const focusable = createFocusable(order, { predicatesElement: true })(element.children[i]);
2188
2220
  if (focusable)
2189
2221
  return focusable;
2190
2222
  }
2191
- break;
2192
- case "last":
2223
+ };
2224
+ case "last":
2225
+ return (element) => {
2226
+ if (predicatesElement && predicateFocusable(element))
2227
+ return element;
2193
2228
  for (let i = element.children.length - 1; i > -1; i--) {
2194
2229
  const focusable = createFocusable(order, { predicatesElement: true })(element.children[i]);
2195
2230
  if (focusable)
2196
2231
  return focusable;
2197
2232
  }
2198
- break;
2199
- }
2200
- };
2233
+ };
2234
+ case "next":
2235
+ return (element) => {
2236
+ if (predicatesElement && predicateFocusable(element))
2237
+ return element;
2238
+ const focusable = createFocusable("first")(element);
2239
+ if (focusable)
2240
+ return focusable;
2241
+ let current = element;
2242
+ while (current && current !== document.documentElement) {
2243
+ const nextSibling = current.nextElementSibling;
2244
+ if (nextSibling) {
2245
+ const focusable2 = createFocusable("first", { predicatesElement: true })(nextSibling);
2246
+ if (focusable2)
2247
+ return focusable2;
2248
+ }
2249
+ current = current.parentElement;
2250
+ }
2251
+ };
2252
+ case "previous":
2253
+ return (element) => {
2254
+ if (predicatesElement && predicateFocusable(element))
2255
+ return element;
2256
+ let current = element;
2257
+ while (current && current !== document.documentElement) {
2258
+ const previousSibling = current.previousElementSibling;
2259
+ if (previousSibling) {
2260
+ const focusable = createFocusable("last", { predicatesElement: true })(previousSibling);
2261
+ if (focusable)
2262
+ return focusable;
2263
+ }
2264
+ current = current.parentElement;
2265
+ }
2266
+ };
2267
+ }
2201
2268
  }
2202
2269
  function createComputedStyle(pseudoElement) {
2203
2270
  return (element) => getComputedStyle(element, pseudoElement);
@@ -2624,7 +2691,7 @@ function createKeyState({
2624
2691
  return some(
2625
2692
  (validAlias) => includes(validAlias)(aliases)
2626
2693
  )(validAliases);
2627
- }, cleanup = () => {
2694
+ }, stop = () => {
2628
2695
  window.cancelAnimationFrame(getRequest());
2629
2696
  }, statuses = [], toStatus = (...params) => createValue(...params)(statuses), setStatus = (...params) => createSet(...params)(statuses), clearStatuses = (...params) => createClear(...params)(statuses), deleteStatus = (...params) => createDelete(...params)(statuses);
2630
2697
  return {
@@ -2636,7 +2703,7 @@ function createKeyState({
2636
2703
  validAliases,
2637
2704
  getDownCombos,
2638
2705
  predicateValid,
2639
- cleanup,
2706
+ stop,
2640
2707
  statuses,
2641
2708
  toStatus,
2642
2709
  setStatus,
@@ -2762,6 +2829,11 @@ function storeKeyboardTimeMetadata({
2762
2829
  effect(event);
2763
2830
  }
2764
2831
  }, storeDuration = () => {
2832
+ const sequence = api.getSequence();
2833
+ if (!document.body.contains(
2834
+ at(-1)(sequence).target
2835
+ ))
2836
+ return;
2765
2837
  const request = requestAnimationFrame((timestamp) => {
2766
2838
  if (!getShouldStore())
2767
2839
  return;
@@ -2881,6 +2953,11 @@ function storePointerTimeMetadata(event, api, getShouldStore, setRequest, recogn
2881
2953
  if (getStatus() === "recognized")
2882
2954
  effect(event2);
2883
2955
  }, storeDuration = () => {
2956
+ const sequence = api.getSequence();
2957
+ if (!document.body.contains(
2958
+ at(-1)(sequence).target
2959
+ ))
2960
+ return;
2884
2961
  const request = requestAnimationFrame((timestamp) => {
2885
2962
  if (!getShouldStore())
2886
2963
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baleada/logic",
3
- "version": "0.24.9",
3
+ "version": "0.24.11",
4
4
  "description": "UI logic for the Baleada toolkit",
5
5
  "main": "lib/index.cjs",
6
6
  "module": "lib/index.js",