@mantine-vue/hooks 3.5.0 → 3.7.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,14 @@ 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 resolveElement(value) {
122
+ if (!value) return null;
123
+ return "$el" in value ? value.$el ?? null : value;
124
+ }
125
+ function useClickOutside(callback, events = ["mousedown", "touchstart"], nodes, enabled = true, capture = false) {
122
126
  const element = (0, vue.ref)(null);
123
127
  let activeEvents = [];
128
+ let activeCapture = false;
124
129
  const listener = (event) => {
125
130
  const target = event.target;
126
131
  if (!target || !document.body.contains(target) && target.tagName !== "HTML") return;
@@ -128,20 +133,28 @@ function useClickOutside(callback, events = ["mousedown", "touchstart"], nodes,
128
133
  const resolvedNodes = nodes ? (0, vue.toValue)(nodes) : void 0;
129
134
  if (resolvedNodes) {
130
135
  if (resolvedNodes.every((node) => !!node && !path.includes(node))) callback(event);
131
- } else if (element.value && !path.includes(element.value)) callback(event);
136
+ } else {
137
+ const targetElement = resolveElement(element.value);
138
+ if (targetElement && !path.includes(targetElement)) callback(event);
139
+ }
132
140
  };
133
141
  const cleanup = () => {
134
- activeEvents.forEach((event) => document.removeEventListener(event, listener));
142
+ activeEvents.forEach((event) => document.removeEventListener(event, listener, activeCapture));
135
143
  activeEvents = [];
136
144
  };
137
145
  const bind = () => {
138
146
  cleanup();
139
147
  if (!(0, vue.toValue)(enabled)) return;
140
148
  activeEvents = (0, vue.toValue)(events) || ["mousedown", "touchstart"];
141
- activeEvents.forEach((event) => document.addEventListener(event, listener));
149
+ activeCapture = (0, vue.toValue)(capture);
150
+ activeEvents.forEach((event) => document.addEventListener(event, listener, activeCapture));
142
151
  };
143
152
  (0, vue.onMounted)(bind);
144
- (0, vue.watch)([() => (0, vue.toValue)(events)?.join(","), () => (0, vue.toValue)(enabled)], bind);
153
+ (0, vue.watch)([
154
+ () => (0, vue.toValue)(events)?.join(","),
155
+ () => (0, vue.toValue)(enabled),
156
+ () => (0, vue.toValue)(capture)
157
+ ], bind);
145
158
  (0, vue.onBeforeUnmount)(cleanup);
146
159
  return element;
147
160
  }
@@ -185,7 +198,11 @@ function useClipboard({ timeout = 2e3 } = {}) {
185
198
  //#region src/use-media-query/use-media-query.ts
186
199
  function getInitialMediaQueryValue(query, initialValue) {
187
200
  if (typeof initialValue === "boolean") return initialValue;
188
- if (typeof window !== "undefined" && typeof window.matchMedia === "function") return window.matchMedia(query).matches;
201
+ if (typeof window !== "undefined" && typeof window.matchMedia === "function") try {
202
+ return window.matchMedia(query).matches;
203
+ } catch {
204
+ return false;
205
+ }
189
206
  return false;
190
207
  }
191
208
  function useMediaQuery(query, initialValue = false, options = { getInitialValueInEffect: true }) {
@@ -255,6 +272,10 @@ function useDebouncedState(initialValue, wait, options = {}) {
255
272
  if (options.leading && !leadingCalled) {
256
273
  value.value = next;
257
274
  leadingCalled = true;
275
+ clear();
276
+ timeout = setTimeout(() => {
277
+ leadingCalled = false;
278
+ }, wait);
258
279
  } else {
259
280
  clear();
260
281
  timeout = setTimeout(() => {
@@ -504,12 +525,8 @@ function useForceUpdate() {
504
525
  //#region src/use-id/use-id.ts
505
526
  var idCounter = 0;
506
527
  function useId(staticId) {
507
- const id = (0, vue.ref)(staticId || "");
508
- (0, vue.onMounted)(() => {
509
- if (!id.value) id.value = `mantine-vue-${++idCounter}`;
510
- });
511
- if (staticId) id.value = staticId;
512
- return id;
528
+ if (staticId) return (0, vue.ref)(staticId);
529
+ return (0, vue.ref)(`mantine-vue-${(0, vue.getCurrentInstance)() ? (0, vue.useId)() : `fallback-${++idCounter}`}`);
513
530
  }
514
531
  //#endregion
515
532
  //#region src/use-idle/use-idle.ts
@@ -1026,7 +1043,7 @@ function usePagination({ total, siblings = 1, boundaries = 1, page, initialPage,
1026
1043
  const leftSiblingIndex = Math.max(active.value - siblings, start.value + boundaries - 1);
1027
1044
  const rightSiblingIndex = Math.min(active.value + siblings, end.value - boundaries);
1028
1045
  const shouldShowLeftDots = leftSiblingIndex > start.value + boundaries + 1;
1029
- const shouldShowRightDots = rightSiblingIndex < end.value - boundaries;
1046
+ const shouldShowRightDots = rightSiblingIndex < end.value - (boundaries + 1);
1030
1047
  if (!shouldShowLeftDots && shouldShowRightDots) {
1031
1048
  const leftItemCount = siblings * 2 + boundaries + 2;
1032
1049
  return [
@@ -1432,8 +1449,8 @@ function useIntersection(options) {
1432
1449
  entry.value = null;
1433
1450
  return;
1434
1451
  }
1435
- observer = new IntersectionObserver(([_entry]) => {
1436
- entry.value = _entry;
1452
+ observer = new IntersectionObserver((entries) => {
1453
+ entry.value = entries[entries.length - 1];
1437
1454
  }, options);
1438
1455
  observer.observe(node);
1439
1456
  }, { immediate: true });
@@ -1536,12 +1553,10 @@ function getHotkeyMatcher(hotkey, usePhysicalKeys) {
1536
1553
  }
1537
1554
  function getHotkeyHandler(hotkeys) {
1538
1555
  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();
1556
+ hotkeys.forEach(([hotkey, handler, options]) => {
1557
+ const { preventDefault = true, usePhysicalKeys = false } = options || {};
1558
+ if (getHotkeyMatcher(hotkey, usePhysicalKeys)(event)) {
1559
+ if (preventDefault) event.preventDefault();
1545
1560
  handler(event);
1546
1561
  }
1547
1562
  });
@@ -1572,16 +1587,14 @@ function useHotkeys(hotkeys, tagsToIgnore = [
1572
1587
  "select"
1573
1588
  ], triggerOnContentEditable = false) {
1574
1589
  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) ?? [
1590
+ (0, vue.toValue)(hotkeys).forEach(([hotkey, handler, options]) => {
1591
+ const { preventDefault = true, usePhysicalKeys = false } = options || {};
1592
+ if (getHotkeyMatcher(hotkey, usePhysicalKeys)(event) && shouldFireEvent(event, (0, vue.toValue)(tagsToIgnore) ?? [
1580
1593
  "input",
1581
1594
  "textarea",
1582
1595
  "select"
1583
1596
  ], (0, vue.toValue)(triggerOnContentEditable))) {
1584
- if (options.preventDefault) event.preventDefault();
1597
+ if (preventDefault) event.preventDefault();
1585
1598
  handler(event);
1586
1599
  }
1587
1600
  });
@@ -1700,20 +1713,25 @@ function useLogger(componentName, props) {
1700
1713
  function useHover() {
1701
1714
  const refTarget = (0, vue.ref)(null);
1702
1715
  const hovered = (0, vue.ref)(false);
1703
- let cleanup;
1704
- (0, vue.onMounted)(() => {
1705
- const node = refTarget.value;
1716
+ (0, vue.watch)(refTarget, (node, _, onCleanup) => {
1717
+ hovered.value = false;
1706
1718
  if (!node) return;
1707
- const onEnter = () => hovered.value = true;
1708
- const onLeave = () => hovered.value = false;
1719
+ const onEnter = () => {
1720
+ hovered.value = true;
1721
+ };
1722
+ const onLeave = () => {
1723
+ hovered.value = false;
1724
+ };
1709
1725
  node.addEventListener("mouseenter", onEnter);
1710
1726
  node.addEventListener("mouseleave", onLeave);
1711
- cleanup = () => {
1727
+ onCleanup(() => {
1712
1728
  node.removeEventListener("mouseenter", onEnter);
1713
1729
  node.removeEventListener("mouseleave", onLeave);
1714
- };
1730
+ });
1731
+ }, {
1732
+ immediate: true,
1733
+ flush: "sync"
1715
1734
  });
1716
- (0, vue.onBeforeUnmount)(() => cleanup?.());
1717
1735
  return {
1718
1736
  ref: refTarget,
1719
1737
  hovered
@@ -1839,27 +1857,35 @@ function useDisclosure(initialState = false, options = {}) {
1839
1857
  function useFocusWithin(options = {}) {
1840
1858
  const refTarget = (0, vue.ref)(null);
1841
1859
  const focused = (0, vue.ref)(false);
1842
- let cleanup;
1843
- (0, vue.onMounted)(() => {
1844
- const node = refTarget.value;
1860
+ (0, vue.watch)(refTarget, (node, _, onCleanup) => {
1861
+ focused.value = false;
1845
1862
  if (!node) return;
1863
+ let active = true;
1846
1864
  const onFocusIn = (event) => {
1847
- focused.value = true;
1848
- options.onFocus?.(event);
1865
+ if (!focused.value) {
1866
+ focused.value = true;
1867
+ options.onFocus?.(event);
1868
+ }
1869
+ };
1870
+ const onFocusOut = (event) => {
1871
+ (0, vue.nextTick)(() => {
1872
+ if (!active) return;
1873
+ const isFocused = node.matches(":focus-within");
1874
+ if (!isFocused && focused.value) options.onBlur?.(event);
1875
+ focused.value = isFocused;
1876
+ });
1849
1877
  };
1850
- const onFocusOut = (event) => (0, vue.nextTick)(() => {
1851
- const isFocused = node.matches(":focus-within");
1852
- if (!isFocused && focused.value) options.onBlur?.(event);
1853
- focused.value = isFocused;
1854
- });
1855
1878
  node.addEventListener("focusin", onFocusIn);
1856
1879
  node.addEventListener("focusout", onFocusOut);
1857
- cleanup = () => {
1880
+ onCleanup(() => {
1881
+ active = false;
1858
1882
  node.removeEventListener("focusin", onFocusIn);
1859
1883
  node.removeEventListener("focusout", onFocusOut);
1860
- };
1884
+ });
1885
+ }, {
1886
+ immediate: true,
1887
+ flush: "sync"
1861
1888
  });
1862
- (0, vue.onBeforeUnmount)(() => cleanup?.());
1863
1889
  return {
1864
1890
  ref: refTarget,
1865
1891
  focused
@@ -2279,7 +2305,13 @@ function useThrottledCallbackWithClearTimeout(callback, wait) {
2279
2305
  let latestOutArgs = null;
2280
2306
  let active = true;
2281
2307
  let timeoutId = -1;
2282
- const clear = () => window.clearTimeout(timeoutId);
2308
+ const clear = () => {
2309
+ window.clearTimeout(timeoutId);
2310
+ timeoutId = -1;
2311
+ active = true;
2312
+ latestInArgs = null;
2313
+ latestOutArgs = null;
2314
+ };
2283
2315
  const callThrottledCallback = (...args) => {
2284
2316
  callback(...args);
2285
2317
  latestInArgs = args;
@@ -3301,7 +3333,7 @@ function findPrevTokenIndex(slots, from) {
3301
3333
  for (let i = from; i >= 0; i--) if (slots[i].type === "token") return i;
3302
3334
  return -1;
3303
3335
  }
3304
- function processInput(inputValue, slots, _slotCharOption) {
3336
+ function processInput(inputValue, slots, _slotCharOption, transform) {
3305
3337
  let result = "";
3306
3338
  let inputIndex = 0;
3307
3339
  for (let slotIndex = 0; slotIndex < slots.length && inputIndex <= inputValue.length; slotIndex++) {
@@ -3313,7 +3345,7 @@ function processInput(inputValue, slots, _slotCharOption) {
3313
3345
  }
3314
3346
  if (inputIndex >= inputValue.length) break;
3315
3347
  while (inputIndex < inputValue.length) {
3316
- const ch = inputValue[inputIndex];
3348
+ const ch = transform ? transform(inputValue[inputIndex]) : inputValue[inputIndex];
3317
3349
  inputIndex++;
3318
3350
  if (slot.pattern.test(ch)) {
3319
3351
  result += ch;
@@ -3435,8 +3467,8 @@ function useMask(options) {
3435
3467
  const initializeInputValue = (inputNode) => {
3436
3468
  if (!inputNode.value) return false;
3437
3469
  const isOwnValue = inputNode.value === display;
3438
- const parse = (slots, slotChar) => isOwnValue ? applyMaskToRaw(rawInternal, slots, slotChar, options.transform) : processInput(inputNode.value, slots, slotChar);
3439
- const { slots: initialSlots, slotChar: initialSlotChar } = getResolvedOptions(options, "");
3470
+ const { slots: initialSlots, slotChar: initialSlotChar, transform } = getResolvedOptions(options, "");
3471
+ const parse = (slots, slotChar) => isOwnValue ? applyMaskToRaw(rawInternal, slots, slotChar, options.transform) : processInput(inputNode.value, slots, slotChar, transform);
3440
3472
  const { slots: resolvedSlots, slotChar } = getResolvedOptions(options, extractRaw(parse(initialSlots, initialSlotChar), initialSlots));
3441
3473
  const reprocessed = parse(resolvedSlots, slotChar);
3442
3474
  const newRaw = extractRaw(reprocessed, resolvedSlots);
@@ -3781,6 +3813,13 @@ function findLastEnabled(total, isItemDisabled) {
3781
3813
  for (let i = total - 1; i >= 0; i -= 1) if (!isItemDisabled(i)) return i;
3782
3814
  return 0;
3783
3815
  }
3816
+ function findEnabledInRow(row, startCol, step, columns, total, isItemDisabled) {
3817
+ for (let col = startCol; col >= 0 && col < columns; col += step) {
3818
+ const candidate = row * columns + col;
3819
+ if (candidate < total && !isItemDisabled(candidate)) return candidate;
3820
+ }
3821
+ return null;
3822
+ }
3784
3823
  var defaultIsItemDisabled = () => false;
3785
3824
  function useRovingIndex(input) {
3786
3825
  const { total, orientation = "horizontal", loop = true, dir = "ltr", activateOnFocus = false, columns, focusedIndex, initialIndex, onFocusChange, isItemDisabled = defaultIsItemDisabled } = input;
@@ -3819,19 +3858,13 @@ function useRovingIndex(input) {
3819
3858
  const isRtl = dir === "rtl";
3820
3859
  switch (event.key) {
3821
3860
  case "ArrowRight": {
3822
- const targetCol = isRtl ? col - 1 : col + 1;
3823
- if (targetCol >= 0 && targetCol < cols && row * cols + targetCol < total_) {
3824
- const candidate = row * cols + targetCol;
3825
- if (!isItemDisabled(candidate)) nextIndex = candidate;
3826
- }
3861
+ const step = isRtl ? -1 : 1;
3862
+ nextIndex = findEnabledInRow(row, col + step, step, cols, total_, isItemDisabled);
3827
3863
  break;
3828
3864
  }
3829
3865
  case "ArrowLeft": {
3830
- const targetCol = isRtl ? col + 1 : col - 1;
3831
- if (targetCol >= 0 && targetCol < cols && row * cols + targetCol < total_) {
3832
- const candidate = row * cols + targetCol;
3833
- if (!isItemDisabled(candidate)) nextIndex = candidate;
3834
- }
3866
+ const step = isRtl ? 1 : -1;
3867
+ nextIndex = findEnabledInRow(row, col + step, step, cols, total_, isItemDisabled);
3835
3868
  break;
3836
3869
  }
3837
3870
  case "ArrowDown":
@@ -4169,6 +4202,7 @@ function useDrag(handler, options = {}) {
4169
4202
  resetDrag();
4170
4203
  };
4171
4204
  const refCallback = (newNode) => {
4205
+ if (newNode === node) return;
4172
4206
  cleanupNode?.();
4173
4207
  cleanupNode = void 0;
4174
4208
  node = newNode;