@directus/composables 11.2.17 → 11.4.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Component, ComputedRef, Ref, WritableComputedRef } from "vue";
1
+ import { Component, ComponentPublicInstance, ComputedRef, Ref, WritableComputedRef } from "vue";
2
2
  import { AxiosInstance } from "axios";
3
3
  import { AppCollection, AppExtensionConfigs, Field, Item, LayoutConfig, Query, RefRecord } from "@directus/types";
4
4
  import { DirectusClient, RestClient } from "@directus/sdk";
@@ -763,6 +763,15 @@ declare function useSizeClass<T>(props: T & SizeProps): ComputedRef<string | nul
763
763
  */
764
764
  declare function useSync<T, K extends keyof T & string, E extends (event: `update:${K}`, ...args: any[]) => void>(props: T, key: K, emit: E): Ref<T[K]>;
765
765
  //#endregion
766
+ //#region src/translate-shortcut.d.ts
767
+ declare function translateShortcut(keys: string[]): string;
768
+ //#endregion
769
+ //#region src/use-shortcut.d.ts
770
+ type ShortcutHandler = (event: KeyboardEvent, cancelNext: () => void) => void | any | boolean;
771
+ declare const keyMap: Record<string, string>;
772
+ declare const systemKeys: string[];
773
+ declare function useShortcut(shortcuts: string | string[], handler: ShortcutHandler, reference?: Ref<HTMLElement | undefined> | Ref<ComponentPublicInstance | undefined>): void;
774
+ //#endregion
766
775
  //#region src/use-system.d.ts
767
776
  /**
768
777
  * Vue composable that provides access to the global Directus stores through dependency injection.
@@ -1065,4 +1074,4 @@ declare function useSdk<Schema extends object = any>(): DirectusClient<Schema> &
1065
1074
  */
1066
1075
  declare function useExtensions(): RefRecord<AppExtensionConfigs>;
1067
1076
  //#endregion
1068
- export { ComputedQuery, GroupableInstance, GroupableOptions, ManualSortData, OtherValue, UsableCollection, UsableCustomSelection, UsableGroupable, UsableItems, createLayoutWrapper, isWritableProp, sizeProps, useApi, useCollection, useCustomSelection, useCustomSelectionMultiple, useElementSize, useExtensions, useFilterFields, useGroupable, useGroupableParent, useItems, useLayout, useSdk, useSizeClass, useStores, useSync };
1077
+ export { ComputedQuery, GroupableInstance, GroupableOptions, ManualSortData, OtherValue, UsableCollection, UsableCustomSelection, UsableGroupable, UsableItems, createLayoutWrapper, isWritableProp, keyMap, sizeProps, systemKeys, translateShortcut, useApi, useCollection, useCustomSelection, useCustomSelectionMultiple, useElementSize, useExtensions, useFilterFields, useGroupable, useGroupableParent, useItems, useLayout, useSdk, useShortcut, useSizeClass, useStores, useSync };
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { computed, defineComponent, inject, isRef, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, reactive, ref, shallowRef, toRef, toRefs, unref, watch } from "vue";
2
2
  import { API_INJECT, EXTENSIONS_INJECT, SDK_INJECT, STORES_INJECT } from "@directus/constants";
3
3
  import { nanoid } from "nanoid";
4
- import { isEqual, isNil, throttle } from "lodash-es";
4
+ import { capitalize, isEmpty, isEqual, isNil, throttle } from "lodash-es";
5
5
  import { getEndpoint, moveInArray } from "@directus/utils";
6
+ import { useMemoize } from "@vueuse/core";
6
7
  import axios from "axios";
7
8
 
8
9
  //#region src/use-system.ts
@@ -884,12 +885,24 @@ function useItems(collection, query) {
884
885
  if (itemCount.value < (unref(limit) ?? 100)) return 1;
885
886
  return Math.ceil(itemCount.value / (unref(limit) ?? 100));
886
887
  });
887
- const existingRequests = {
888
- items: null,
889
- total: null,
890
- filter: null
891
- };
888
+ let itemsAbort = null;
889
+ let totalCountGeneration = 0;
890
+ let itemCountGeneration = 0;
892
891
  let loadingTimeout = null;
892
+ const fetchAggregate = useMemoize(async (url, filter$1, search$1) => {
893
+ const aggregate = primaryKeyField.value ? { countDistinct: primaryKeyField.value.field } : { count: "*" };
894
+ const response = await api.get(url, { params: {
895
+ aggregate,
896
+ ...filter$1 ? { filter: filter$1 } : {},
897
+ ...search$1 ? { search: search$1 } : {}
898
+ } });
899
+ return primaryKeyField.value ? Number(response.data.data[0].countDistinct[primaryKeyField.value.field]) : Number(response.data.data[0].count);
900
+ }, { getKey(url, filter$1, search$1) {
901
+ const key = { url };
902
+ if (!isEmpty(filter$1)) key.filter = filter$1;
903
+ if (!isEmpty(search$1)) key.search = search$1;
904
+ return JSON.stringify(key);
905
+ } });
893
906
  const fetchItems = throttle((shouldUpdateCount) => {
894
907
  Promise.all([getItems(), shouldUpdateCount ? getItemCount() : Promise.resolve()]);
895
908
  }, 500);
@@ -940,8 +953,8 @@ function useItems(collection, query) {
940
953
  async function getItems() {
941
954
  if (!endpoint.value) return;
942
955
  let isCurrentRequestCanceled = false;
943
- if (existingRequests.items) existingRequests.items.abort();
944
- existingRequests.items = new AbortController();
956
+ if (itemsAbort) itemsAbort.abort();
957
+ itemsAbort = new AbortController();
945
958
  error.value = null;
946
959
  if (loadingTimeout) clearTimeout(loadingTimeout);
947
960
  loadingTimeout = setTimeout(() => {
@@ -962,9 +975,9 @@ function useItems(collection, query) {
962
975
  filter: unref(filter),
963
976
  deep: unref(deep)
964
977
  },
965
- signal: existingRequests.items.signal
978
+ signal: itemsAbort.signal
966
979
  })).data.data;
967
- existingRequests.items = null;
980
+ itemsAbort = null;
968
981
  /**
969
982
  * @NOTE
970
983
  *
@@ -1011,19 +1024,10 @@ function useItems(collection, query) {
1011
1024
  }
1012
1025
  async function getTotalCount() {
1013
1026
  if (!endpoint.value) return;
1027
+ const currentGeneration = ++totalCountGeneration;
1014
1028
  try {
1015
- if (existingRequests.total) existingRequests.total.abort();
1016
- existingRequests.total = new AbortController();
1017
- const aggregate = primaryKeyField.value ? { countDistinct: primaryKeyField.value.field } : { count: "*" };
1018
- const response = await api.get(endpoint.value, {
1019
- params: {
1020
- aggregate,
1021
- filter: unref(filterSystem)
1022
- },
1023
- signal: existingRequests.total.signal
1024
- });
1025
- const count = primaryKeyField.value ? Number(response.data.data[0].countDistinct[primaryKeyField.value.field]) : Number(response.data.data[0].count);
1026
- existingRequests.total = null;
1029
+ const count = await fetchAggregate(endpoint.value, filterSystem?.value, void 0);
1030
+ if (currentGeneration !== totalCountGeneration) return;
1027
1031
  totalCount.value = count;
1028
1032
  } catch (err) {
1029
1033
  if (!axios.isCancel(err)) throw err;
@@ -1031,26 +1035,16 @@ function useItems(collection, query) {
1031
1035
  }
1032
1036
  async function getItemCount() {
1033
1037
  if (!endpoint.value) return;
1038
+ const currentGeneration = ++itemCountGeneration;
1034
1039
  loadingItemCount.value = true;
1035
1040
  try {
1036
- if (existingRequests.filter) existingRequests.filter.abort();
1037
- existingRequests.filter = new AbortController();
1038
- const aggregate = primaryKeyField.value ? { countDistinct: primaryKeyField.value.field } : { count: "*" };
1039
- const response = await api.get(endpoint.value, {
1040
- params: {
1041
- filter: unref(filter),
1042
- search: unref(search),
1043
- aggregate
1044
- },
1045
- signal: existingRequests.filter.signal
1046
- });
1047
- const count = primaryKeyField.value ? Number(response.data.data[0].countDistinct[primaryKeyField.value.field]) : Number(response.data.data[0].count);
1048
- existingRequests.filter = null;
1041
+ const count = await fetchAggregate(endpoint.value, filter.value, search.value);
1042
+ if (currentGeneration !== itemCountGeneration) return;
1049
1043
  itemCount.value = count;
1050
1044
  } catch (err) {
1051
1045
  if (!axios.isCancel(err)) throw err;
1052
1046
  } finally {
1053
- loadingItemCount.value = false;
1047
+ if (currentGeneration === itemCountGeneration) loadingItemCount.value = false;
1054
1048
  }
1055
1049
  }
1056
1050
  }
@@ -1503,4 +1497,99 @@ function useSync(props, key, emit) {
1503
1497
  }
1504
1498
 
1505
1499
  //#endregion
1506
- export { createLayoutWrapper, isWritableProp, sizeProps, useApi, useCollection, useCustomSelection, useCustomSelectionMultiple, useElementSize, useExtensions, useFilterFields, useGroupable, useGroupableParent, useItems, useLayout, useSdk, useSizeClass, useStores, useSync };
1500
+ //#region src/translate-shortcut.ts
1501
+ function translateShortcut(keys) {
1502
+ if (navigator.platform.toLowerCase().startsWith("mac") || navigator.platform.startsWith("iP")) return keys.map((key) => {
1503
+ if (key === "meta") return "⌘";
1504
+ if (key === "option") return "⌥";
1505
+ if (key === "shift") return "⇧";
1506
+ if (key === "alt") return "⌥";
1507
+ if (key === "enter") return "⏎";
1508
+ return capitalize(key);
1509
+ }).join("");
1510
+ else return keys.map((key) => {
1511
+ if (key === "meta") return "Ctrl";
1512
+ if (key === "enter") return "↵";
1513
+ return capitalize(key);
1514
+ }).join("+");
1515
+ }
1516
+
1517
+ //#endregion
1518
+ //#region src/use-shortcut.ts
1519
+ const keyMap = {
1520
+ Control: "meta",
1521
+ Command: "meta"
1522
+ };
1523
+ const systemKeys = [
1524
+ "meta",
1525
+ "shift",
1526
+ "alt",
1527
+ "backspace",
1528
+ "delete",
1529
+ "tab",
1530
+ "capslock",
1531
+ "enter",
1532
+ "home",
1533
+ "end"
1534
+ ];
1535
+ const keysDown = /* @__PURE__ */ new Set([]);
1536
+ const handlers = {};
1537
+ document.body.addEventListener("keydown", (event) => {
1538
+ if (event.repeat || !event.key) return;
1539
+ keysDown.add(mapKeys(event));
1540
+ callHandlers(event);
1541
+ });
1542
+ document.body.addEventListener("keyup", (event) => {
1543
+ if (event.repeat || !event.key) return;
1544
+ keysDown.clear();
1545
+ });
1546
+ function useShortcut(shortcuts, handler, reference = ref(document.body)) {
1547
+ const callback = (event, cancelNext) => {
1548
+ if (!reference.value) return;
1549
+ const ref$1 = reference.value instanceof HTMLElement ? reference.value : reference.value.$el;
1550
+ if (document.activeElement === ref$1 || ref$1.contains(document.activeElement) || document.activeElement === document.body) {
1551
+ event.preventDefault();
1552
+ return handler(event, cancelNext);
1553
+ }
1554
+ return false;
1555
+ };
1556
+ onMounted(() => {
1557
+ [shortcuts].flat().forEach((shortcut) => {
1558
+ if (shortcut in handlers) handlers[shortcut]?.unshift(callback);
1559
+ else handlers[shortcut] = [callback];
1560
+ });
1561
+ });
1562
+ onUnmounted(() => {
1563
+ [shortcuts].flat().forEach((shortcut) => {
1564
+ const shortcutHandler = handlers[shortcut];
1565
+ if (shortcutHandler) {
1566
+ const filteredHandlers = shortcutHandler.filter((f) => f !== callback);
1567
+ handlers[shortcut] = filteredHandlers;
1568
+ if (filteredHandlers.length === 0) delete handlers[shortcut];
1569
+ }
1570
+ });
1571
+ });
1572
+ }
1573
+ function mapKeys(key) {
1574
+ let keyString = key.key.match(/^[a-zA-Z0-9]*?$/g) === null ? key.code.replace(/(Key|Digit)/g, "") : key.key;
1575
+ keyString = keyMap[keyString] ?? keyString;
1576
+ keyString = keyString.toLowerCase();
1577
+ return keyString;
1578
+ }
1579
+ function callHandlers(event) {
1580
+ Object.entries(handlers).forEach(([key, value]) => {
1581
+ const keys = key.split("+");
1582
+ for (key of keysDown) if (keys.includes(key) === false) return;
1583
+ for (key of keys) if (keysDown.has(key) === false) return;
1584
+ for (let i = 0; i < value.length; i++) {
1585
+ let cancel = false;
1586
+ value[i]?.(event, () => {
1587
+ cancel = true;
1588
+ });
1589
+ if (typeof cancel === "boolean" && cancel) break;
1590
+ }
1591
+ });
1592
+ }
1593
+
1594
+ //#endregion
1595
+ export { createLayoutWrapper, isWritableProp, keyMap, sizeProps, systemKeys, translateShortcut, useApi, useCollection, useCustomSelection, useCustomSelectionMultiple, useElementSize, useExtensions, useFilterFields, useGroupable, useGroupableParent, useItems, useLayout, useSdk, useShortcut, useSizeClass, useStores, useSync };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/composables",
3
- "version": "11.2.17",
3
+ "version": "11.4.0",
4
4
  "description": "Shared Vue composables for Directus use",
5
5
  "homepage": "https://directus.io",
6
6
  "repository": {
@@ -22,11 +22,12 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "axios": "1.13.5",
26
- "lodash-es": "4.17.23",
25
+ "@vueuse/core": "14.0.0",
26
+ "axios": "1.15.0",
27
+ "lodash-es": "4.18.1",
27
28
  "nanoid": "5.1.6",
28
- "@directus/constants": "14.3.0",
29
- "@directus/utils": "13.4.0"
29
+ "@directus/utils": "13.4.0",
30
+ "@directus/constants": "14.3.0"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@directus/tsconfig": "4.0.0",
@@ -37,9 +38,9 @@
37
38
  "typescript": "5.9.3",
38
39
  "vitest": "3.2.4",
39
40
  "vue": "3.5.24",
40
- "@directus/sdk": "21.2.2",
41
- "@directus/extensions": "3.0.23",
42
- "@directus/types": "15.0.1"
41
+ "@directus/extensions": "3.0.24",
42
+ "@directus/types": "15.0.2",
43
+ "@directus/sdk": "21.2.2"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "vue": "3.5.24"