@mantine-vue/hooks 3.4.0 → 3.6.0

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/cjs/index.cjs CHANGED
@@ -118,9 +118,10 @@ function useDebouncedCallback(callback, waitOrOptions) {
118
118
  }
119
119
  //#endregion
120
120
  //#region src/use-click-outside/use-click-outside.ts
121
- function useClickOutside(callback, events = ["mousedown", "touchstart"], nodes, enabled = true) {
121
+ function useClickOutside(callback, events = ["mousedown", "touchstart"], nodes, enabled = true, capture = false) {
122
122
  const element = (0, vue.ref)(null);
123
123
  let activeEvents = [];
124
+ let activeCapture = false;
124
125
  const listener = (event) => {
125
126
  const target = event.target;
126
127
  if (!target || !document.body.contains(target) && target.tagName !== "HTML") return;
@@ -131,17 +132,22 @@ function useClickOutside(callback, events = ["mousedown", "touchstart"], nodes,
131
132
  } else if (element.value && !path.includes(element.value)) callback(event);
132
133
  };
133
134
  const cleanup = () => {
134
- activeEvents.forEach((event) => document.removeEventListener(event, listener));
135
+ activeEvents.forEach((event) => document.removeEventListener(event, listener, activeCapture));
135
136
  activeEvents = [];
136
137
  };
137
138
  const bind = () => {
138
139
  cleanup();
139
140
  if (!(0, vue.toValue)(enabled)) return;
140
141
  activeEvents = (0, vue.toValue)(events) || ["mousedown", "touchstart"];
141
- activeEvents.forEach((event) => document.addEventListener(event, listener));
142
+ activeCapture = (0, vue.toValue)(capture);
143
+ activeEvents.forEach((event) => document.addEventListener(event, listener, activeCapture));
142
144
  };
143
145
  (0, vue.onMounted)(bind);
144
- (0, vue.watch)([() => (0, vue.toValue)(events)?.join(","), () => (0, vue.toValue)(enabled)], bind);
146
+ (0, vue.watch)([
147
+ () => (0, vue.toValue)(events)?.join(","),
148
+ () => (0, vue.toValue)(enabled),
149
+ () => (0, vue.toValue)(capture)
150
+ ], bind);
145
151
  (0, vue.onBeforeUnmount)(cleanup);
146
152
  return element;
147
153
  }
@@ -185,7 +191,11 @@ function useClipboard({ timeout = 2e3 } = {}) {
185
191
  //#region src/use-media-query/use-media-query.ts
186
192
  function getInitialMediaQueryValue(query, initialValue) {
187
193
  if (typeof initialValue === "boolean") return initialValue;
188
- if (typeof window !== "undefined" && typeof window.matchMedia === "function") return window.matchMedia(query).matches;
194
+ if (typeof window !== "undefined" && typeof window.matchMedia === "function") try {
195
+ return window.matchMedia(query).matches;
196
+ } catch {
197
+ return false;
198
+ }
189
199
  return false;
190
200
  }
191
201
  function useMediaQuery(query, initialValue = false, options = { getInitialValueInEffect: true }) {
@@ -255,6 +265,10 @@ function useDebouncedState(initialValue, wait, options = {}) {
255
265
  if (options.leading && !leadingCalled) {
256
266
  value.value = next;
257
267
  leadingCalled = true;
268
+ clear();
269
+ timeout = setTimeout(() => {
270
+ leadingCalled = false;
271
+ }, wait);
258
272
  } else {
259
273
  clear();
260
274
  timeout = setTimeout(() => {
@@ -1026,7 +1040,7 @@ function usePagination({ total, siblings = 1, boundaries = 1, page, initialPage,
1026
1040
  const leftSiblingIndex = Math.max(active.value - siblings, start.value + boundaries - 1);
1027
1041
  const rightSiblingIndex = Math.min(active.value + siblings, end.value - boundaries);
1028
1042
  const shouldShowLeftDots = leftSiblingIndex > start.value + boundaries + 1;
1029
- const shouldShowRightDots = rightSiblingIndex < end.value - boundaries;
1043
+ const shouldShowRightDots = rightSiblingIndex < end.value - (boundaries + 1);
1030
1044
  if (!shouldShowLeftDots && shouldShowRightDots) {
1031
1045
  const leftItemCount = siblings * 2 + boundaries + 2;
1032
1046
  return [
@@ -1432,8 +1446,8 @@ function useIntersection(options) {
1432
1446
  entry.value = null;
1433
1447
  return;
1434
1448
  }
1435
- observer = new IntersectionObserver(([_entry]) => {
1436
- entry.value = _entry;
1449
+ observer = new IntersectionObserver((entries) => {
1450
+ entry.value = entries[entries.length - 1];
1437
1451
  }, options);
1438
1452
  observer.observe(node);
1439
1453
  }, { immediate: true });
@@ -1536,12 +1550,10 @@ function getHotkeyMatcher(hotkey, usePhysicalKeys) {
1536
1550
  }
1537
1551
  function getHotkeyHandler(hotkeys) {
1538
1552
  return (event) => {
1539
- hotkeys.forEach(([hotkey, handler, options = {
1540
- preventDefault: true,
1541
- usePhysicalKeys: false
1542
- }]) => {
1543
- if (getHotkeyMatcher(hotkey, options.usePhysicalKeys)(event)) {
1544
- if (options.preventDefault) event.preventDefault();
1553
+ hotkeys.forEach(([hotkey, handler, options]) => {
1554
+ const { preventDefault = true, usePhysicalKeys = false } = options || {};
1555
+ if (getHotkeyMatcher(hotkey, usePhysicalKeys)(event)) {
1556
+ if (preventDefault) event.preventDefault();
1545
1557
  handler(event);
1546
1558
  }
1547
1559
  });
@@ -1572,16 +1584,14 @@ function useHotkeys(hotkeys, tagsToIgnore = [
1572
1584
  "select"
1573
1585
  ], triggerOnContentEditable = false) {
1574
1586
  const handleKeydown = (event) => {
1575
- (0, vue.toValue)(hotkeys).forEach(([hotkey, handler, options = {
1576
- preventDefault: true,
1577
- usePhysicalKeys: false
1578
- }]) => {
1579
- if (getHotkeyMatcher(hotkey, options.usePhysicalKeys)(event) && shouldFireEvent(event, (0, vue.toValue)(tagsToIgnore) ?? [
1587
+ (0, vue.toValue)(hotkeys).forEach(([hotkey, handler, options]) => {
1588
+ const { preventDefault = true, usePhysicalKeys = false } = options || {};
1589
+ if (getHotkeyMatcher(hotkey, usePhysicalKeys)(event) && shouldFireEvent(event, (0, vue.toValue)(tagsToIgnore) ?? [
1580
1590
  "input",
1581
1591
  "textarea",
1582
1592
  "select"
1583
1593
  ], (0, vue.toValue)(triggerOnContentEditable))) {
1584
- if (options.preventDefault) event.preventDefault();
1594
+ if (preventDefault) event.preventDefault();
1585
1595
  handler(event);
1586
1596
  }
1587
1597
  });
@@ -2279,7 +2289,13 @@ function useThrottledCallbackWithClearTimeout(callback, wait) {
2279
2289
  let latestOutArgs = null;
2280
2290
  let active = true;
2281
2291
  let timeoutId = -1;
2282
- const clear = () => window.clearTimeout(timeoutId);
2292
+ const clear = () => {
2293
+ window.clearTimeout(timeoutId);
2294
+ timeoutId = -1;
2295
+ active = true;
2296
+ latestInArgs = null;
2297
+ latestOutArgs = null;
2298
+ };
2283
2299
  const callThrottledCallback = (...args) => {
2284
2300
  callback(...args);
2285
2301
  latestInArgs = args;
@@ -2521,6 +2537,10 @@ function useRadialMove(onChange, { step = .01, onChangeEnd, onScrubStart, onScru
2521
2537
  //#endregion
2522
2538
  //#region src/use-scroll-spy/use-scroll-spy.ts
2523
2539
  var scrollSpyId = 0;
2540
+ function resolveHeadingNode(heading, selector, index) {
2541
+ if (heading.isConnected) return heading;
2542
+ return (heading.id ? document.getElementById(heading.id) : null) ?? document.querySelectorAll(selector)[index] ?? heading;
2543
+ }
2524
2544
  function getActiveElement(rects, offset = 0) {
2525
2545
  if (rects.length === 0) return -1;
2526
2546
  return rects.reduce((closest, rect, index) => Math.abs(closest.position - offset) < Math.abs(rect.y - offset) ? closest : {
@@ -2543,11 +2563,12 @@ function useScrollSpy(options = {}) {
2543
2563
  if (typeof document === "undefined") return;
2544
2564
  const getDepth = options.getDepth ?? ((element) => Number(element.tagName[1]));
2545
2565
  const getValue = options.getValue ?? ((element) => element.textContent || "");
2546
- data.value = Array.from(document.querySelectorAll((0, vue.toValue)(options.selector) ?? "h1, h2, h3, h4, h5, h6")).map((heading) => ({
2566
+ const selector = (0, vue.toValue)(options.selector) ?? "h1, h2, h3, h4, h5, h6";
2567
+ data.value = Array.from(document.querySelectorAll(selector)).map((heading, index) => ({
2547
2568
  depth: getDepth(heading),
2548
2569
  value: getValue(heading),
2549
2570
  id: heading.id || `mantine-scroll-spy-${++scrollSpyId}`,
2550
- getNode: () => heading.id ? document.getElementById(heading.id) ?? heading : heading
2571
+ getNode: () => resolveHeadingNode(heading, selector, index)
2551
2572
  }));
2552
2573
  initialized.value = true;
2553
2574
  updateActive();
@@ -3041,6 +3062,9 @@ function getElementContentSize(element, dimension) {
3041
3062
  function getCssSize(size) {
3042
3063
  return typeof size === "number" ? `${size}px` : size;
3043
3064
  }
3065
+ function isMeasured(size) {
3066
+ return typeof size === "number" && size > 0;
3067
+ }
3044
3068
  function createCollapse({ dimension, getElementSize, expanded, transitionDuration, transitionTimingFunction = "ease", onTransitionEnd, onTransitionStart, keepMounted }) {
3045
3069
  const isExpanded = () => (0, vue.toValue)(expanded);
3046
3070
  const getTransitionDuration = () => (0, vue.toValue)(transitionDuration);
@@ -3054,17 +3078,24 @@ function createCollapse({ dimension, getElementSize, expanded, transitionDuratio
3054
3078
  const styles = (0, vue.ref)(isExpanded() ? {} : collapsedStyles);
3055
3079
  const state = (0, vue.ref)(isExpanded() ? "entered" : "exited");
3056
3080
  const raf = typeof window === "undefined" ? (callback) => callback(0) : window.requestAnimationFrame;
3081
+ let transitionId = 0;
3057
3082
  const mergeStyles = (nextStyles) => {
3058
3083
  styles.value = {
3059
3084
  ...styles.value,
3060
3085
  ...nextStyles
3061
3086
  };
3062
3087
  };
3063
- const measureExpandedSize = (attempt = 0) => {
3064
- if (!isExpanded()) return;
3088
+ const measureExpandedSize = (currentTransitionId, attempt = 0) => {
3089
+ if (transitionId !== currentTransitionId || !isExpanded() || !elementRef.value) return;
3065
3090
  const size = getElementSize(elementRef.value);
3066
3091
  if (size === 0 && elementRef.value?.children.length && attempt < 5) {
3067
- raf(() => measureExpandedSize(attempt + 1));
3092
+ raf(() => measureExpandedSize(currentTransitionId, attempt + 1));
3093
+ return;
3094
+ }
3095
+ if (!isMeasured(size)) {
3096
+ styles.value = {};
3097
+ state.value = "entered";
3098
+ onTransitionEnd?.();
3068
3099
  return;
3069
3100
  }
3070
3101
  mergeStyles({
@@ -3078,8 +3109,12 @@ function createCollapse({ dimension, getElementSize, expanded, transitionDuratio
3078
3109
  };
3079
3110
  (0, vue.watch)(() => isExpanded(), (value, oldValue) => {
3080
3111
  if (value === oldValue) return;
3112
+ transitionId += 1;
3113
+ const currentTransitionId = transitionId;
3114
+ const isCurrentTransition = () => transitionId === currentTransitionId && elementRef.value !== null;
3081
3115
  if (getTransitionDuration() !== 0) onTransitionStart?.();
3082
3116
  if (value) raf(() => {
3117
+ if (!isCurrentTransition()) return;
3083
3118
  state.value = "entering";
3084
3119
  mergeStyles({
3085
3120
  willChange: dimension,
@@ -3088,24 +3123,37 @@ function createCollapse({ dimension, getElementSize, expanded, transitionDuratio
3088
3123
  });
3089
3124
  (0, vue.nextTick)(() => {
3090
3125
  raf(() => {
3091
- raf(() => measureExpandedSize());
3126
+ if (!isCurrentTransition()) return;
3127
+ raf(() => measureExpandedSize(currentTransitionId));
3092
3128
  });
3093
3129
  });
3094
3130
  });
3095
3131
  else raf(() => {
3132
+ if (!isCurrentTransition()) return;
3096
3133
  state.value = "exiting";
3097
3134
  const size = getElementSize(elementRef.value);
3135
+ if (!isMeasured(size)) {
3136
+ styles.value = collapsedStyles;
3137
+ state.value = "exited";
3138
+ onTransitionEnd?.();
3139
+ return;
3140
+ }
3098
3141
  mergeStyles({
3099
3142
  ...getTransitionStyles(size),
3100
3143
  willChange: dimension,
3101
3144
  [dimension]: getCssSize(size)
3102
3145
  });
3103
- raf(() => mergeStyles({
3104
- [dimension]: 0,
3105
- overflow: "hidden"
3106
- }));
3146
+ raf(() => {
3147
+ if (isCurrentTransition()) mergeStyles({
3148
+ [dimension]: 0,
3149
+ overflow: "hidden"
3150
+ });
3151
+ });
3107
3152
  });
3108
3153
  }, { flush: "post" });
3154
+ (0, vue.onBeforeUnmount)(() => {
3155
+ transitionId += 1;
3156
+ });
3109
3157
  const handleTransitionEnd = (event) => {
3110
3158
  if (event.target !== elementRef.value || event.propertyName !== dimension) return;
3111
3159
  if (isExpanded()) {
@@ -3269,7 +3317,7 @@ function findPrevTokenIndex(slots, from) {
3269
3317
  for (let i = from; i >= 0; i--) if (slots[i].type === "token") return i;
3270
3318
  return -1;
3271
3319
  }
3272
- function processInput(inputValue, slots, _slotCharOption) {
3320
+ function processInput(inputValue, slots, _slotCharOption, transform) {
3273
3321
  let result = "";
3274
3322
  let inputIndex = 0;
3275
3323
  for (let slotIndex = 0; slotIndex < slots.length && inputIndex <= inputValue.length; slotIndex++) {
@@ -3281,7 +3329,7 @@ function processInput(inputValue, slots, _slotCharOption) {
3281
3329
  }
3282
3330
  if (inputIndex >= inputValue.length) break;
3283
3331
  while (inputIndex < inputValue.length) {
3284
- const ch = inputValue[inputIndex];
3332
+ const ch = transform ? transform(inputValue[inputIndex]) : inputValue[inputIndex];
3285
3333
  inputIndex++;
3286
3334
  if (slot.pattern.test(ch)) {
3287
3335
  result += ch;
@@ -3402,9 +3450,11 @@ function useMask(options) {
3402
3450
  };
3403
3451
  const initializeInputValue = (inputNode) => {
3404
3452
  if (!inputNode.value) return false;
3405
- const { slots: initialSlots, slotChar: initialSlotChar } = getResolvedOptions(options, "");
3406
- const { slots: resolvedSlots, slotChar } = getResolvedOptions(options, extractRaw(processInput(inputNode.value, initialSlots, initialSlotChar), initialSlots));
3407
- const reprocessed = processInput(inputNode.value, resolvedSlots, slotChar);
3453
+ const isOwnValue = inputNode.value === display;
3454
+ const { slots: initialSlots, slotChar: initialSlotChar, transform } = getResolvedOptions(options, "");
3455
+ const parse = (slots, slotChar) => isOwnValue ? applyMaskToRaw(rawInternal, slots, slotChar, options.transform) : processInput(inputNode.value, slots, slotChar, transform);
3456
+ const { slots: resolvedSlots, slotChar } = getResolvedOptions(options, extractRaw(parse(initialSlots, initialSlotChar), initialSlots));
3457
+ const reprocessed = parse(resolvedSlots, slotChar);
3408
3458
  const newRaw = extractRaw(reprocessed, resolvedSlots);
3409
3459
  const showSlots = options.alwaysShowMask || isFocused;
3410
3460
  const showOnFocus = options.showMaskOnFocus !== false;
@@ -3747,6 +3797,13 @@ function findLastEnabled(total, isItemDisabled) {
3747
3797
  for (let i = total - 1; i >= 0; i -= 1) if (!isItemDisabled(i)) return i;
3748
3798
  return 0;
3749
3799
  }
3800
+ function findEnabledInRow(row, startCol, step, columns, total, isItemDisabled) {
3801
+ for (let col = startCol; col >= 0 && col < columns; col += step) {
3802
+ const candidate = row * columns + col;
3803
+ if (candidate < total && !isItemDisabled(candidate)) return candidate;
3804
+ }
3805
+ return null;
3806
+ }
3750
3807
  var defaultIsItemDisabled = () => false;
3751
3808
  function useRovingIndex(input) {
3752
3809
  const { total, orientation = "horizontal", loop = true, dir = "ltr", activateOnFocus = false, columns, focusedIndex, initialIndex, onFocusChange, isItemDisabled = defaultIsItemDisabled } = input;
@@ -3785,19 +3842,13 @@ function useRovingIndex(input) {
3785
3842
  const isRtl = dir === "rtl";
3786
3843
  switch (event.key) {
3787
3844
  case "ArrowRight": {
3788
- const targetCol = isRtl ? col - 1 : col + 1;
3789
- if (targetCol >= 0 && targetCol < cols && row * cols + targetCol < total_) {
3790
- const candidate = row * cols + targetCol;
3791
- if (!isItemDisabled(candidate)) nextIndex = candidate;
3792
- }
3845
+ const step = isRtl ? -1 : 1;
3846
+ nextIndex = findEnabledInRow(row, col + step, step, cols, total_, isItemDisabled);
3793
3847
  break;
3794
3848
  }
3795
3849
  case "ArrowLeft": {
3796
- const targetCol = isRtl ? col + 1 : col - 1;
3797
- if (targetCol >= 0 && targetCol < cols && row * cols + targetCol < total_) {
3798
- const candidate = row * cols + targetCol;
3799
- if (!isItemDisabled(candidate)) nextIndex = candidate;
3800
- }
3850
+ const step = isRtl ? 1 : -1;
3851
+ nextIndex = findEnabledInRow(row, col + step, step, cols, total_, isItemDisabled);
3801
3852
  break;
3802
3853
  }
3803
3854
  case "ArrowDown":