@maple-design/use 1.0.0 → 1.1.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/README.md +56 -0
- package/lib/index.cjs +251 -1
- package/lib/index.d.cts +54 -2
- package/lib/index.d.ts +54 -2
- package/lib/index.js +258 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 共用异步数据源
|
|
2
|
+
|
|
3
|
+
`useAsyncData` 适用于人员、字典、表格行、级联根选项等初始化与外部搜索。函数返回业务转换后的数据;composable 不规定 URL、客户端、查询字段或响应 DTO。组件的 options/data 仍是对应类型的数组。
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { useAsyncData } from '@maple-design/use'
|
|
7
|
+
|
|
8
|
+
// 可从业务模块导入,同一个函数可供多处复用。
|
|
9
|
+
const loadPeople = async () => [{ value: 'mengshuai', label: '孟帅' }]
|
|
10
|
+
const { data: options, loading, error, execute, refresh, cancel, clearCache } =
|
|
11
|
+
useAsyncData(loadPeople, { initialData: [], debounce: 250, cacheTime: 30_000 })
|
|
12
|
+
|
|
13
|
+
// 默认客户端挂载执行 loadPeople 一次,初始化不防抖。不会监听函数引用、选中值或数组变化。
|
|
14
|
+
// 数据绑定:<Select :options="options" :loading="loading" @search="search" />
|
|
15
|
+
function search(query: string) {
|
|
16
|
+
options.value = [] // 外部搜索可立即退役旧候选,避免等待期间误选。
|
|
17
|
+
void execute({
|
|
18
|
+
cacheKey: query, // 若身份/权限/部门变化会影响结果,也必须包含在 key 中。
|
|
19
|
+
loader: async ({ signal }) => {
|
|
20
|
+
const response = await fetch(`/api/people?q=${encodeURIComponent(query)}`, { signal })
|
|
21
|
+
if (!response.ok) throw new Error('People request failed')
|
|
22
|
+
const rows = await response.json()
|
|
23
|
+
return rows.map((row: { id: string; name: string }) => ({ value: row.id, label: row.name }))
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 参数和返回值
|
|
30
|
+
|
|
31
|
+
- `AsyncDataLoader<T> = (context: { signal: AbortSignal }) => T | Promise<T>`。零参数函数也可直接传入;原生 fetch 应包装后再传。业务参数由闭包捕获,数据格式转换在函数内部完成。
|
|
32
|
+
- options:必填 `initialData: T`,`immediate` 默认 true,`debounce` 默认 0 毫秒,`cacheTime` 默认 0(关闭)。零参数 loader 推断 T,提供 initialData 时应保持相同类型,空数组可显式标注 `[] as PersonOption[]`。
|
|
33
|
+
- `data` 是可写 shallowRef,保留结果对象身份;`loading`、`error` 只读。composable 不自动深拷贝、冻结或深监听数据。
|
|
34
|
+
- `execute({ loader?, cacheKey?, force? })`:手动执行,接受查询专属 loader,不替换初始化函数。无覆盖 loader 时执行初始 loader。返回 Promise,不自动向外抛异常。
|
|
35
|
+
- 结果为 `{ status: 'success', data }`、`{ status: 'error', error }` 或 `{ status: 'cancelled' }`。调用者可检查 status;自动初始化失败不会产生未处理拒绝。
|
|
36
|
+
- `refresh()`:重试最近一次请求及 key,绕过缓存和防抖。第一次调用时使用初始化 loader。
|
|
37
|
+
- `cancel()`:清除排队计时器、中止 signal、立即结算取消结果,保留 data,不记录错误。
|
|
38
|
+
- `clearCache()`:清空当前实例缓存,不清空展示数据,也不自动发请求。
|
|
39
|
+
|
|
40
|
+
## 生命周期、并发与缓存
|
|
41
|
+
|
|
42
|
+
在组件 setup 调用时,客户端 mounted 执行一次;SSR 不自动请求。在 effectScope 中使用但不属于组件时立即初始化。`immediate: false` 只手动执行。setup 中已手动 execute 的,不会在 mounted 重复初始化。重渲染、选中值变化不会重新执行;卸载/作用域结束后取消并禁止再次执行。重新挂载是新实例,会重新初始化。
|
|
43
|
+
|
|
44
|
+
每次 execute 立即令 loading=true(含防抖排队),取消上次请求;只有最后一次结果能更新 data/error/loading,即使旧客户端忽略 signal 也不能覆盖。初始加载和 refresh 不防抖;组件自身搜索延迟应设为 0,防止两次防抖。异常保留上次 data;远程候选是否清空由业务决定,上例主动清空。取消不会把 AbortError 当业务失败。
|
|
45
|
+
|
|
46
|
+
默认不缓存。启用后仅当前实例缓存,TTL 从成功完成时算起,最多保存 50 项,满后按写入顺序淘汰最早一项;不跨组件、不跨登录用户全局复用,不合并并发请求。初始化 loader 有独立的内部 key;覆盖 loader 只有显式传 cacheKey 才缓存。不通过函数身份推断缓存,不缓存异常或取消。相同函数用于两处意味着两次独立初始化;若业务要求只请求一次,应在父级创建 composable 并把同一 data/loading 传给两处。若 loader 本身返回共享可变对象,其引用仍由业务负责。
|
|
47
|
+
|
|
48
|
+
## 特殊协议
|
|
49
|
+
|
|
50
|
+
本工具不替代节点懒加载、游标分页或上传。Cedar Tree/TreeSelect:`loadMore(node): Promise<void>`;Cedar/Maple Cascader:`loadMore(option, done(children?)): void`;Cedar Table:`loadMore(record, done(children?)): void`。按现有节点插入/完成协议工作,不能把整表初始化 loader 直接当这些回调。
|
|
51
|
+
|
|
52
|
+
Scheduler 保留日程的 modelValue/defaultValue 数组、resources 数组,以及 `requestResources({ dimensionId?, query, cursor?, signal }) -> { items, nextCursor? }` 和 `resolveResources(ids, {signal}) -> Resource[]`;事件与资源不是一个 data。上传保留进度、中止和完成回调。本轮不更改这些已发布协议。
|
|
53
|
+
|
|
54
|
+
## 已发布 API
|
|
55
|
+
|
|
56
|
+
这个工具是 use 包新增的 minor API。各组件原有数组属性保持不变;没有为所有 options/data 加函数或 Promise 联合类型。PeopleSelect 按已确认方案移除旧 fetch/urlType,属于 UI 包下个 major 的迁移,文档提供对照;当前没有执行发布。
|
package/lib/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
8
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
7
9
|
var __getProtoOf = Object.getPrototypeOf;
|
|
@@ -19,6 +21,7 @@ var __spreadValues = (a, b) => {
|
|
|
19
21
|
}
|
|
20
22
|
return a;
|
|
21
23
|
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
22
25
|
var __objRest = (source, exclude) => {
|
|
23
26
|
var target = {};
|
|
24
27
|
for (var prop in source)
|
|
@@ -52,6 +55,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
52
55
|
mod
|
|
53
56
|
));
|
|
54
57
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
58
|
+
var __async = (__this, __arguments, generator) => {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
var fulfilled = (value) => {
|
|
61
|
+
try {
|
|
62
|
+
step(generator.next(value));
|
|
63
|
+
} catch (e) {
|
|
64
|
+
reject(e);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var rejected = (value) => {
|
|
68
|
+
try {
|
|
69
|
+
step(generator.throw(value));
|
|
70
|
+
} catch (e) {
|
|
71
|
+
reject(e);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
75
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
76
|
+
});
|
|
77
|
+
};
|
|
55
78
|
|
|
56
79
|
// src/index.ts
|
|
57
80
|
var src_exports = {};
|
|
@@ -60,9 +83,14 @@ __export(src_exports, {
|
|
|
60
83
|
onSmartMounted: () => onSmartMounted,
|
|
61
84
|
onSmartUnmounted: () => onSmartUnmounted,
|
|
62
85
|
onWindowResize: () => onWindowResize,
|
|
86
|
+
provideDirection: () => provideDirection,
|
|
87
|
+
readingDirectionKey: () => readingDirectionKey,
|
|
88
|
+
resolveDirection: () => resolveDirection,
|
|
89
|
+
useAsyncData: () => useAsyncData,
|
|
63
90
|
useChildren: () => useChildren,
|
|
64
91
|
useClickOutside: () => useClickOutside,
|
|
65
92
|
useClientId: () => useClientId,
|
|
93
|
+
useDirection: () => useDirection,
|
|
66
94
|
useEventListener: () => useEventListener,
|
|
67
95
|
useId: () => useId2,
|
|
68
96
|
useInitialized: () => useInitialized,
|
|
@@ -579,7 +607,7 @@ function useMotion(options) {
|
|
|
579
607
|
return (0, import_shared9.motion)({
|
|
580
608
|
from: getter(options.from),
|
|
581
609
|
to: getter(options.to),
|
|
582
|
-
duration: options.duration ? getter(options.duration)
|
|
610
|
+
duration: options.duration === void 0 ? 300 : getter(options.duration),
|
|
583
611
|
timingFunction: options.timingFunction,
|
|
584
612
|
onStateChange(newState) {
|
|
585
613
|
state.value = newState;
|
|
@@ -601,15 +629,237 @@ function useMotion(options) {
|
|
|
601
629
|
reset
|
|
602
630
|
};
|
|
603
631
|
}
|
|
632
|
+
|
|
633
|
+
// src/useAsyncData.ts
|
|
634
|
+
var import_vue13 = require("vue");
|
|
635
|
+
function useAsyncData(loader, options) {
|
|
636
|
+
const data = (0, import_vue13.shallowRef)(options.initialData);
|
|
637
|
+
const loading = (0, import_vue13.ref)(false);
|
|
638
|
+
const error = (0, import_vue13.shallowRef)();
|
|
639
|
+
const initialKey = Symbol("initial");
|
|
640
|
+
const cache = /* @__PURE__ */ new Map();
|
|
641
|
+
let active;
|
|
642
|
+
let last = {};
|
|
643
|
+
let disposed = false;
|
|
644
|
+
let started = false;
|
|
645
|
+
function cancel() {
|
|
646
|
+
const task = active;
|
|
647
|
+
active = void 0;
|
|
648
|
+
if (task) {
|
|
649
|
+
clearTimeout(task.timer);
|
|
650
|
+
task.controller.abort();
|
|
651
|
+
task.settle({ status: "cancelled" });
|
|
652
|
+
}
|
|
653
|
+
loading.value = false;
|
|
654
|
+
}
|
|
655
|
+
function run(request, delay) {
|
|
656
|
+
var _a, _b, _c;
|
|
657
|
+
cancel();
|
|
658
|
+
if (disposed) {
|
|
659
|
+
return Promise.resolve({ status: "cancelled" });
|
|
660
|
+
}
|
|
661
|
+
started = true;
|
|
662
|
+
last = request;
|
|
663
|
+
error.value = void 0;
|
|
664
|
+
const currentLoader = (_a = request.loader) != null ? _a : loader;
|
|
665
|
+
const key = (_b = request.cacheKey) != null ? _b : request.loader ? void 0 : initialKey;
|
|
666
|
+
const ttl = (_c = options.cacheTime) != null ? _c : 0;
|
|
667
|
+
const cached = key === void 0 ? void 0 : cache.get(key);
|
|
668
|
+
if (!request.force && ttl > 0 && cached && cached.expires > Date.now()) {
|
|
669
|
+
data.value = cached.data;
|
|
670
|
+
return Promise.resolve({ status: "success", data: cached.data });
|
|
671
|
+
}
|
|
672
|
+
if (key !== void 0) {
|
|
673
|
+
cache.delete(key);
|
|
674
|
+
}
|
|
675
|
+
loading.value = true;
|
|
676
|
+
return new Promise((settle) => {
|
|
677
|
+
const task = {
|
|
678
|
+
controller: new AbortController(),
|
|
679
|
+
settle,
|
|
680
|
+
timer: void 0
|
|
681
|
+
};
|
|
682
|
+
active = task;
|
|
683
|
+
const start = () => __async(this, null, function* () {
|
|
684
|
+
try {
|
|
685
|
+
const value = yield currentLoader({ signal: task.controller.signal });
|
|
686
|
+
if (active !== task) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
data.value = value;
|
|
690
|
+
if (ttl > 0 && key !== void 0) {
|
|
691
|
+
if (cache.size >= 50) {
|
|
692
|
+
cache.delete(cache.keys().next().value);
|
|
693
|
+
}
|
|
694
|
+
cache.set(key, { data: value, expires: Date.now() + ttl });
|
|
695
|
+
}
|
|
696
|
+
settle({ status: "success", data: value });
|
|
697
|
+
} catch (cause) {
|
|
698
|
+
if (active !== task) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
error.value = cause;
|
|
702
|
+
settle({ status: "error", error: cause });
|
|
703
|
+
} finally {
|
|
704
|
+
if (active === task) {
|
|
705
|
+
active = void 0;
|
|
706
|
+
loading.value = false;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
if (delay > 0) {
|
|
711
|
+
task.timer = setTimeout(start, delay);
|
|
712
|
+
} else {
|
|
713
|
+
void start();
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
const execute = (request = {}) => {
|
|
718
|
+
var _a;
|
|
719
|
+
return run(request, (_a = options.debounce) != null ? _a : 0);
|
|
720
|
+
};
|
|
721
|
+
const refresh = () => run(__spreadProps(__spreadValues({}, last), { force: true }), 0);
|
|
722
|
+
const initialize = () => {
|
|
723
|
+
if (!started && !disposed && options.immediate !== false) {
|
|
724
|
+
void run({}, 0);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
if ((0, import_vue13.getCurrentInstance)()) {
|
|
728
|
+
(0, import_vue13.onMounted)(initialize);
|
|
729
|
+
} else {
|
|
730
|
+
initialize();
|
|
731
|
+
}
|
|
732
|
+
if ((0, import_vue13.getCurrentScope)()) {
|
|
733
|
+
(0, import_vue13.onScopeDispose)(() => {
|
|
734
|
+
disposed = true;
|
|
735
|
+
cancel();
|
|
736
|
+
cache.clear();
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
data,
|
|
741
|
+
loading: (0, import_vue13.readonly)(loading),
|
|
742
|
+
error: (0, import_vue13.readonly)(error),
|
|
743
|
+
execute,
|
|
744
|
+
refresh,
|
|
745
|
+
cancel,
|
|
746
|
+
clearCache: () => cache.clear()
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/useDirection.ts
|
|
751
|
+
var import_vue14 = require("vue");
|
|
752
|
+
var readingDirectionKey = Symbol("reading-direction");
|
|
753
|
+
function provideDirection(direction) {
|
|
754
|
+
(0, import_vue14.provide)(readingDirectionKey, direction);
|
|
755
|
+
}
|
|
756
|
+
function resolveDirection(element, fallback = "ltr") {
|
|
757
|
+
var _a, _b;
|
|
758
|
+
const scope = element == null ? void 0 : element.closest("[dir]");
|
|
759
|
+
const value = (_a = scope == null ? void 0 : scope.getAttribute("dir")) == null ? void 0 : _a.toLowerCase();
|
|
760
|
+
if (value === "ltr" || value === "rtl") {
|
|
761
|
+
return value;
|
|
762
|
+
}
|
|
763
|
+
if (value === "auto" && scope) {
|
|
764
|
+
return ((_b = scope.ownerDocument.defaultView) == null ? void 0 : _b.getComputedStyle(scope).direction) === "rtl" ? "rtl" : "ltr";
|
|
765
|
+
}
|
|
766
|
+
return fallback;
|
|
767
|
+
}
|
|
768
|
+
var documents = /* @__PURE__ */ new WeakMap();
|
|
769
|
+
function useDirection(target, explicit) {
|
|
770
|
+
const inherited = (0, import_vue14.inject)(readingDirectionKey, void 0);
|
|
771
|
+
const instance = (0, import_vue14.getCurrentInstance)();
|
|
772
|
+
const element = (0, import_vue14.shallowRef)();
|
|
773
|
+
const version = (0, import_vue14.shallowRef)();
|
|
774
|
+
let release;
|
|
775
|
+
let active = false;
|
|
776
|
+
function observe(node) {
|
|
777
|
+
var _a;
|
|
778
|
+
if (node === element.value && release) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
release == null ? void 0 : release();
|
|
782
|
+
release = void 0;
|
|
783
|
+
element.value = node != null ? node : void 0;
|
|
784
|
+
version.value = void 0;
|
|
785
|
+
const doc = node == null ? void 0 : node.ownerDocument;
|
|
786
|
+
const Observer = (_a = doc == null ? void 0 : doc.defaultView) == null ? void 0 : _a.MutationObserver;
|
|
787
|
+
if (!doc || !Observer) {
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
let state = documents.get(doc);
|
|
791
|
+
if (!state) {
|
|
792
|
+
const counter = (0, import_vue14.ref)(0);
|
|
793
|
+
const observer = new Observer(() => {
|
|
794
|
+
counter.value++;
|
|
795
|
+
});
|
|
796
|
+
observer.observe(doc.documentElement, { subtree: true, attributes: true, attributeFilter: ["dir"] });
|
|
797
|
+
state = { version: counter, observer, users: 0 };
|
|
798
|
+
documents.set(doc, state);
|
|
799
|
+
}
|
|
800
|
+
state.users++;
|
|
801
|
+
version.value = state.version;
|
|
802
|
+
const current = state;
|
|
803
|
+
release = () => {
|
|
804
|
+
if (--current.users === 0) {
|
|
805
|
+
current.observer.disconnect();
|
|
806
|
+
documents.delete(doc);
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function refresh() {
|
|
811
|
+
const node = target ? target() : instance == null ? void 0 : instance.vnode.el;
|
|
812
|
+
observe((node == null ? void 0 : node.nodeType) === 1 ? node : node == null ? void 0 : node.parentElement);
|
|
813
|
+
}
|
|
814
|
+
function start() {
|
|
815
|
+
active = true;
|
|
816
|
+
refresh();
|
|
817
|
+
}
|
|
818
|
+
function stop() {
|
|
819
|
+
active = false;
|
|
820
|
+
release == null ? void 0 : release();
|
|
821
|
+
release = void 0;
|
|
822
|
+
version.value = void 0;
|
|
823
|
+
}
|
|
824
|
+
(0, import_vue14.onMounted)(start);
|
|
825
|
+
(0, import_vue14.onActivated)(start);
|
|
826
|
+
(0, import_vue14.onDeactivated)(stop);
|
|
827
|
+
(0, import_vue14.onScopeDispose)(stop);
|
|
828
|
+
(0, import_vue14.watchPostEffect)(() => {
|
|
829
|
+
const node = target == null ? void 0 : target();
|
|
830
|
+
if (active) {
|
|
831
|
+
if (target) {
|
|
832
|
+
observe(node);
|
|
833
|
+
} else {
|
|
834
|
+
refresh();
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
const dir = (0, import_vue14.computed)(() => {
|
|
839
|
+
var _a, _b;
|
|
840
|
+
void ((_a = version.value) == null ? void 0 : _a.value);
|
|
841
|
+
const value = explicit == null ? void 0 : explicit();
|
|
842
|
+
if (value === "ltr" || value === "rtl") {
|
|
843
|
+
return value;
|
|
844
|
+
}
|
|
845
|
+
return resolveDirection(element.value, (_b = inherited == null ? void 0 : inherited.value) != null ? _b : "ltr");
|
|
846
|
+
});
|
|
847
|
+
return { dir, isRtl: (0, import_vue14.computed)(() => dir.value === "rtl") };
|
|
848
|
+
}
|
|
604
849
|
// Annotate the CommonJS export names for ESM import in node:
|
|
605
850
|
0 && (module.exports = {
|
|
606
851
|
keyInProvides,
|
|
607
852
|
onSmartMounted,
|
|
608
853
|
onSmartUnmounted,
|
|
609
854
|
onWindowResize,
|
|
855
|
+
provideDirection,
|
|
856
|
+
readingDirectionKey,
|
|
857
|
+
resolveDirection,
|
|
858
|
+
useAsyncData,
|
|
610
859
|
useChildren,
|
|
611
860
|
useClickOutside,
|
|
612
861
|
useClientId,
|
|
862
|
+
useDirection,
|
|
613
863
|
useEventListener,
|
|
614
864
|
useId,
|
|
615
865
|
useInitialized,
|
package/lib/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
|
-
import { Ref, ComponentInternalInstance, ComputedRef, WatchSource } from 'vue';
|
|
2
|
+
import { Ref, ComponentInternalInstance, ComputedRef, WatchSource, ShallowRef, InjectionKey } from 'vue';
|
|
3
3
|
import { MotionState } from '@maple-design/shared';
|
|
4
4
|
|
|
5
5
|
type UseEventListenerTarget = EventTarget | Ref<EventTarget | undefined | null> | (() => EventTarget | undefined | null);
|
|
@@ -106,4 +106,56 @@ declare function useMotion(options: UseMotionOptions): {
|
|
|
106
106
|
reset: () => void;
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
interface AsyncDataContext {
|
|
110
|
+
signal: AbortSignal;
|
|
111
|
+
}
|
|
112
|
+
/** A zero-argument business function is also accepted; mapping stays in the loader. */
|
|
113
|
+
type AsyncDataLoader<T> = (context: AsyncDataContext) => T | Promise<T>;
|
|
114
|
+
type AsyncDataResult<T> = {
|
|
115
|
+
status: 'success';
|
|
116
|
+
data: T;
|
|
117
|
+
} | {
|
|
118
|
+
status: 'cancelled';
|
|
119
|
+
} | {
|
|
120
|
+
status: 'error';
|
|
121
|
+
error: unknown;
|
|
122
|
+
};
|
|
123
|
+
interface AsyncDataOptions<T> {
|
|
124
|
+
initialData: T;
|
|
125
|
+
/** Run once on client mount. Changing the loader does not rerun it. */
|
|
126
|
+
immediate?: boolean;
|
|
127
|
+
/** Milliseconds; applies to manual execute calls, not initial loading or refresh. */
|
|
128
|
+
debounce?: number;
|
|
129
|
+
/** Per-instance TTL in milliseconds. Disabled by default; at most 50 entries. */
|
|
130
|
+
cacheTime?: number;
|
|
131
|
+
}
|
|
132
|
+
interface AsyncDataExecuteOptions<T> {
|
|
133
|
+
loader?: AsyncDataLoader<T>;
|
|
134
|
+
/** Explicitly distinguish query, tenant, permissions, etc. Never inferred from a function. */
|
|
135
|
+
cacheKey?: string;
|
|
136
|
+
force?: boolean;
|
|
137
|
+
}
|
|
138
|
+
interface AsyncDataReturn<T> {
|
|
139
|
+
data: ShallowRef<T>;
|
|
140
|
+
loading: Readonly<Ref<boolean>>;
|
|
141
|
+
error: Readonly<ShallowRef<unknown>>;
|
|
142
|
+
execute: (request?: AsyncDataExecuteOptions<T>) => Promise<AsyncDataResult<T>>;
|
|
143
|
+
refresh: () => Promise<AsyncDataResult<T>>;
|
|
144
|
+
cancel: () => void;
|
|
145
|
+
clearCache: () => void;
|
|
146
|
+
}
|
|
147
|
+
/** Array/object agnostic async state. Each invocation owns its state and cache. */
|
|
148
|
+
declare function useAsyncData<T>(loader: AsyncDataLoader<T>, options: AsyncDataOptions<T>): AsyncDataReturn<T>;
|
|
149
|
+
|
|
150
|
+
type ReadingDirection = 'ltr' | 'rtl';
|
|
151
|
+
declare const readingDirectionKey: InjectionKey<Readonly<Ref<ReadingDirection | undefined>>>;
|
|
152
|
+
/** Provide reading direction without changing the direction of the document. */
|
|
153
|
+
declare function provideDirection(direction: Readonly<Ref<ReadingDirection | undefined>>): void;
|
|
154
|
+
declare function resolveDirection(element?: Element | null, fallback?: ReadingDirection): ReadingDirection;
|
|
155
|
+
/** DOM scopes take precedence over injection, so native nested dir containers work too. */
|
|
156
|
+
declare function useDirection(target?: () => Element | null | undefined, explicit?: () => unknown): {
|
|
157
|
+
dir: vue.ComputedRef<ReadingDirection>;
|
|
158
|
+
isRtl: vue.ComputedRef<boolean>;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export { AsyncDataContext, AsyncDataExecuteOptions, AsyncDataLoader, AsyncDataOptions, AsyncDataResult, AsyncDataReturn, ReadingDirection, TouchDirection, UseChildrenBaseProvider, UseClickOutsideTarget, UseEventListenerOptions, UseEventListenerTarget, UseMotionOptions, UseVModelOptions, UseWindowSizeOptions, keyInProvides, onSmartMounted, onSmartUnmounted, onWindowResize, provideDirection, readingDirectionKey, resolveDirection, useAsyncData, useChildren, useClickOutside, useClientId, useDirection, useEventListener, useId, useInitialized, useMotion, useParent, useTouch, useVModel, useWindowSize };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
|
-
import { Ref, ComponentInternalInstance, ComputedRef, WatchSource } from 'vue';
|
|
2
|
+
import { Ref, ComponentInternalInstance, ComputedRef, WatchSource, ShallowRef, InjectionKey } from 'vue';
|
|
3
3
|
import { MotionState } from '@maple-design/shared';
|
|
4
4
|
|
|
5
5
|
type UseEventListenerTarget = EventTarget | Ref<EventTarget | undefined | null> | (() => EventTarget | undefined | null);
|
|
@@ -106,4 +106,56 @@ declare function useMotion(options: UseMotionOptions): {
|
|
|
106
106
|
reset: () => void;
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
interface AsyncDataContext {
|
|
110
|
+
signal: AbortSignal;
|
|
111
|
+
}
|
|
112
|
+
/** A zero-argument business function is also accepted; mapping stays in the loader. */
|
|
113
|
+
type AsyncDataLoader<T> = (context: AsyncDataContext) => T | Promise<T>;
|
|
114
|
+
type AsyncDataResult<T> = {
|
|
115
|
+
status: 'success';
|
|
116
|
+
data: T;
|
|
117
|
+
} | {
|
|
118
|
+
status: 'cancelled';
|
|
119
|
+
} | {
|
|
120
|
+
status: 'error';
|
|
121
|
+
error: unknown;
|
|
122
|
+
};
|
|
123
|
+
interface AsyncDataOptions<T> {
|
|
124
|
+
initialData: T;
|
|
125
|
+
/** Run once on client mount. Changing the loader does not rerun it. */
|
|
126
|
+
immediate?: boolean;
|
|
127
|
+
/** Milliseconds; applies to manual execute calls, not initial loading or refresh. */
|
|
128
|
+
debounce?: number;
|
|
129
|
+
/** Per-instance TTL in milliseconds. Disabled by default; at most 50 entries. */
|
|
130
|
+
cacheTime?: number;
|
|
131
|
+
}
|
|
132
|
+
interface AsyncDataExecuteOptions<T> {
|
|
133
|
+
loader?: AsyncDataLoader<T>;
|
|
134
|
+
/** Explicitly distinguish query, tenant, permissions, etc. Never inferred from a function. */
|
|
135
|
+
cacheKey?: string;
|
|
136
|
+
force?: boolean;
|
|
137
|
+
}
|
|
138
|
+
interface AsyncDataReturn<T> {
|
|
139
|
+
data: ShallowRef<T>;
|
|
140
|
+
loading: Readonly<Ref<boolean>>;
|
|
141
|
+
error: Readonly<ShallowRef<unknown>>;
|
|
142
|
+
execute: (request?: AsyncDataExecuteOptions<T>) => Promise<AsyncDataResult<T>>;
|
|
143
|
+
refresh: () => Promise<AsyncDataResult<T>>;
|
|
144
|
+
cancel: () => void;
|
|
145
|
+
clearCache: () => void;
|
|
146
|
+
}
|
|
147
|
+
/** Array/object agnostic async state. Each invocation owns its state and cache. */
|
|
148
|
+
declare function useAsyncData<T>(loader: AsyncDataLoader<T>, options: AsyncDataOptions<T>): AsyncDataReturn<T>;
|
|
149
|
+
|
|
150
|
+
type ReadingDirection = 'ltr' | 'rtl';
|
|
151
|
+
declare const readingDirectionKey: InjectionKey<Readonly<Ref<ReadingDirection | undefined>>>;
|
|
152
|
+
/** Provide reading direction without changing the direction of the document. */
|
|
153
|
+
declare function provideDirection(direction: Readonly<Ref<ReadingDirection | undefined>>): void;
|
|
154
|
+
declare function resolveDirection(element?: Element | null, fallback?: ReadingDirection): ReadingDirection;
|
|
155
|
+
/** DOM scopes take precedence over injection, so native nested dir containers work too. */
|
|
156
|
+
declare function useDirection(target?: () => Element | null | undefined, explicit?: () => unknown): {
|
|
157
|
+
dir: vue.ComputedRef<ReadingDirection>;
|
|
158
|
+
isRtl: vue.ComputedRef<boolean>;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export { AsyncDataContext, AsyncDataExecuteOptions, AsyncDataLoader, AsyncDataOptions, AsyncDataResult, AsyncDataReturn, ReadingDirection, TouchDirection, UseChildrenBaseProvider, UseClickOutsideTarget, UseEventListenerOptions, UseEventListenerTarget, UseMotionOptions, UseVModelOptions, UseWindowSizeOptions, keyInProvides, onSmartMounted, onSmartUnmounted, onWindowResize, provideDirection, readingDirectionKey, resolveDirection, useAsyncData, useChildren, useClickOutside, useClientId, useDirection, useEventListener, useId, useInitialized, useMotion, useParent, useTouch, useVModel, useWindowSize };
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
2
4
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
6
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -14,6 +16,7 @@ var __spreadValues = (a, b) => {
|
|
|
14
16
|
}
|
|
15
17
|
return a;
|
|
16
18
|
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
17
20
|
var __objRest = (source, exclude) => {
|
|
18
21
|
var target = {};
|
|
19
22
|
for (var prop in source)
|
|
@@ -26,6 +29,26 @@ var __objRest = (source, exclude) => {
|
|
|
26
29
|
}
|
|
27
30
|
return target;
|
|
28
31
|
};
|
|
32
|
+
var __async = (__this, __arguments, generator) => {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
var fulfilled = (value) => {
|
|
35
|
+
try {
|
|
36
|
+
step(generator.next(value));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
reject(e);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var rejected = (value) => {
|
|
42
|
+
try {
|
|
43
|
+
step(generator.throw(value));
|
|
44
|
+
} catch (e) {
|
|
45
|
+
reject(e);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
49
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
50
|
+
});
|
|
51
|
+
};
|
|
29
52
|
|
|
30
53
|
// src/useEventListener.ts
|
|
31
54
|
import { isRef, onBeforeUnmount, onDeactivated, unref, watch } from "vue";
|
|
@@ -548,7 +571,7 @@ function useMotion(options) {
|
|
|
548
571
|
return motion({
|
|
549
572
|
from: getter(options.from),
|
|
550
573
|
to: getter(options.to),
|
|
551
|
-
duration: options.duration ? getter(options.duration)
|
|
574
|
+
duration: options.duration === void 0 ? 300 : getter(options.duration),
|
|
552
575
|
timingFunction: options.timingFunction,
|
|
553
576
|
onStateChange(newState) {
|
|
554
577
|
state.value = newState;
|
|
@@ -570,14 +593,248 @@ function useMotion(options) {
|
|
|
570
593
|
reset
|
|
571
594
|
};
|
|
572
595
|
}
|
|
596
|
+
|
|
597
|
+
// src/useAsyncData.ts
|
|
598
|
+
import { getCurrentInstance as getCurrentInstance5, getCurrentScope, onMounted as onMounted5, onScopeDispose, readonly, ref as ref8, shallowRef } from "vue";
|
|
599
|
+
function useAsyncData(loader, options) {
|
|
600
|
+
const data = shallowRef(options.initialData);
|
|
601
|
+
const loading = ref8(false);
|
|
602
|
+
const error = shallowRef();
|
|
603
|
+
const initialKey = Symbol("initial");
|
|
604
|
+
const cache = /* @__PURE__ */ new Map();
|
|
605
|
+
let active;
|
|
606
|
+
let last = {};
|
|
607
|
+
let disposed = false;
|
|
608
|
+
let started = false;
|
|
609
|
+
function cancel() {
|
|
610
|
+
const task = active;
|
|
611
|
+
active = void 0;
|
|
612
|
+
if (task) {
|
|
613
|
+
clearTimeout(task.timer);
|
|
614
|
+
task.controller.abort();
|
|
615
|
+
task.settle({ status: "cancelled" });
|
|
616
|
+
}
|
|
617
|
+
loading.value = false;
|
|
618
|
+
}
|
|
619
|
+
function run(request, delay) {
|
|
620
|
+
var _a, _b, _c;
|
|
621
|
+
cancel();
|
|
622
|
+
if (disposed) {
|
|
623
|
+
return Promise.resolve({ status: "cancelled" });
|
|
624
|
+
}
|
|
625
|
+
started = true;
|
|
626
|
+
last = request;
|
|
627
|
+
error.value = void 0;
|
|
628
|
+
const currentLoader = (_a = request.loader) != null ? _a : loader;
|
|
629
|
+
const key = (_b = request.cacheKey) != null ? _b : request.loader ? void 0 : initialKey;
|
|
630
|
+
const ttl = (_c = options.cacheTime) != null ? _c : 0;
|
|
631
|
+
const cached = key === void 0 ? void 0 : cache.get(key);
|
|
632
|
+
if (!request.force && ttl > 0 && cached && cached.expires > Date.now()) {
|
|
633
|
+
data.value = cached.data;
|
|
634
|
+
return Promise.resolve({ status: "success", data: cached.data });
|
|
635
|
+
}
|
|
636
|
+
if (key !== void 0) {
|
|
637
|
+
cache.delete(key);
|
|
638
|
+
}
|
|
639
|
+
loading.value = true;
|
|
640
|
+
return new Promise((settle) => {
|
|
641
|
+
const task = {
|
|
642
|
+
controller: new AbortController(),
|
|
643
|
+
settle,
|
|
644
|
+
timer: void 0
|
|
645
|
+
};
|
|
646
|
+
active = task;
|
|
647
|
+
const start = () => __async(this, null, function* () {
|
|
648
|
+
try {
|
|
649
|
+
const value = yield currentLoader({ signal: task.controller.signal });
|
|
650
|
+
if (active !== task) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
data.value = value;
|
|
654
|
+
if (ttl > 0 && key !== void 0) {
|
|
655
|
+
if (cache.size >= 50) {
|
|
656
|
+
cache.delete(cache.keys().next().value);
|
|
657
|
+
}
|
|
658
|
+
cache.set(key, { data: value, expires: Date.now() + ttl });
|
|
659
|
+
}
|
|
660
|
+
settle({ status: "success", data: value });
|
|
661
|
+
} catch (cause) {
|
|
662
|
+
if (active !== task) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
error.value = cause;
|
|
666
|
+
settle({ status: "error", error: cause });
|
|
667
|
+
} finally {
|
|
668
|
+
if (active === task) {
|
|
669
|
+
active = void 0;
|
|
670
|
+
loading.value = false;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
if (delay > 0) {
|
|
675
|
+
task.timer = setTimeout(start, delay);
|
|
676
|
+
} else {
|
|
677
|
+
void start();
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
const execute = (request = {}) => {
|
|
682
|
+
var _a;
|
|
683
|
+
return run(request, (_a = options.debounce) != null ? _a : 0);
|
|
684
|
+
};
|
|
685
|
+
const refresh = () => run(__spreadProps(__spreadValues({}, last), { force: true }), 0);
|
|
686
|
+
const initialize = () => {
|
|
687
|
+
if (!started && !disposed && options.immediate !== false) {
|
|
688
|
+
void run({}, 0);
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
if (getCurrentInstance5()) {
|
|
692
|
+
onMounted5(initialize);
|
|
693
|
+
} else {
|
|
694
|
+
initialize();
|
|
695
|
+
}
|
|
696
|
+
if (getCurrentScope()) {
|
|
697
|
+
onScopeDispose(() => {
|
|
698
|
+
disposed = true;
|
|
699
|
+
cancel();
|
|
700
|
+
cache.clear();
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
data,
|
|
705
|
+
loading: readonly(loading),
|
|
706
|
+
error: readonly(error),
|
|
707
|
+
execute,
|
|
708
|
+
refresh,
|
|
709
|
+
cancel,
|
|
710
|
+
clearCache: () => cache.clear()
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/useDirection.ts
|
|
715
|
+
import {
|
|
716
|
+
computed as computed4,
|
|
717
|
+
getCurrentInstance as getCurrentInstance6,
|
|
718
|
+
inject as inject2,
|
|
719
|
+
onActivated as onActivated3,
|
|
720
|
+
onDeactivated as onDeactivated5,
|
|
721
|
+
onMounted as onMounted6,
|
|
722
|
+
onScopeDispose as onScopeDispose2,
|
|
723
|
+
provide as provide2,
|
|
724
|
+
ref as ref9,
|
|
725
|
+
shallowRef as shallowRef2,
|
|
726
|
+
watchPostEffect
|
|
727
|
+
} from "vue";
|
|
728
|
+
var readingDirectionKey = Symbol("reading-direction");
|
|
729
|
+
function provideDirection(direction) {
|
|
730
|
+
provide2(readingDirectionKey, direction);
|
|
731
|
+
}
|
|
732
|
+
function resolveDirection(element, fallback = "ltr") {
|
|
733
|
+
var _a, _b;
|
|
734
|
+
const scope = element == null ? void 0 : element.closest("[dir]");
|
|
735
|
+
const value = (_a = scope == null ? void 0 : scope.getAttribute("dir")) == null ? void 0 : _a.toLowerCase();
|
|
736
|
+
if (value === "ltr" || value === "rtl") {
|
|
737
|
+
return value;
|
|
738
|
+
}
|
|
739
|
+
if (value === "auto" && scope) {
|
|
740
|
+
return ((_b = scope.ownerDocument.defaultView) == null ? void 0 : _b.getComputedStyle(scope).direction) === "rtl" ? "rtl" : "ltr";
|
|
741
|
+
}
|
|
742
|
+
return fallback;
|
|
743
|
+
}
|
|
744
|
+
var documents = /* @__PURE__ */ new WeakMap();
|
|
745
|
+
function useDirection(target, explicit) {
|
|
746
|
+
const inherited = inject2(readingDirectionKey, void 0);
|
|
747
|
+
const instance = getCurrentInstance6();
|
|
748
|
+
const element = shallowRef2();
|
|
749
|
+
const version = shallowRef2();
|
|
750
|
+
let release;
|
|
751
|
+
let active = false;
|
|
752
|
+
function observe(node) {
|
|
753
|
+
var _a;
|
|
754
|
+
if (node === element.value && release) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
release == null ? void 0 : release();
|
|
758
|
+
release = void 0;
|
|
759
|
+
element.value = node != null ? node : void 0;
|
|
760
|
+
version.value = void 0;
|
|
761
|
+
const doc = node == null ? void 0 : node.ownerDocument;
|
|
762
|
+
const Observer = (_a = doc == null ? void 0 : doc.defaultView) == null ? void 0 : _a.MutationObserver;
|
|
763
|
+
if (!doc || !Observer) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
let state = documents.get(doc);
|
|
767
|
+
if (!state) {
|
|
768
|
+
const counter = ref9(0);
|
|
769
|
+
const observer = new Observer(() => {
|
|
770
|
+
counter.value++;
|
|
771
|
+
});
|
|
772
|
+
observer.observe(doc.documentElement, { subtree: true, attributes: true, attributeFilter: ["dir"] });
|
|
773
|
+
state = { version: counter, observer, users: 0 };
|
|
774
|
+
documents.set(doc, state);
|
|
775
|
+
}
|
|
776
|
+
state.users++;
|
|
777
|
+
version.value = state.version;
|
|
778
|
+
const current = state;
|
|
779
|
+
release = () => {
|
|
780
|
+
if (--current.users === 0) {
|
|
781
|
+
current.observer.disconnect();
|
|
782
|
+
documents.delete(doc);
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function refresh() {
|
|
787
|
+
const node = target ? target() : instance == null ? void 0 : instance.vnode.el;
|
|
788
|
+
observe((node == null ? void 0 : node.nodeType) === 1 ? node : node == null ? void 0 : node.parentElement);
|
|
789
|
+
}
|
|
790
|
+
function start() {
|
|
791
|
+
active = true;
|
|
792
|
+
refresh();
|
|
793
|
+
}
|
|
794
|
+
function stop() {
|
|
795
|
+
active = false;
|
|
796
|
+
release == null ? void 0 : release();
|
|
797
|
+
release = void 0;
|
|
798
|
+
version.value = void 0;
|
|
799
|
+
}
|
|
800
|
+
onMounted6(start);
|
|
801
|
+
onActivated3(start);
|
|
802
|
+
onDeactivated5(stop);
|
|
803
|
+
onScopeDispose2(stop);
|
|
804
|
+
watchPostEffect(() => {
|
|
805
|
+
const node = target == null ? void 0 : target();
|
|
806
|
+
if (active) {
|
|
807
|
+
if (target) {
|
|
808
|
+
observe(node);
|
|
809
|
+
} else {
|
|
810
|
+
refresh();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
const dir = computed4(() => {
|
|
815
|
+
var _a, _b;
|
|
816
|
+
void ((_a = version.value) == null ? void 0 : _a.value);
|
|
817
|
+
const value = explicit == null ? void 0 : explicit();
|
|
818
|
+
if (value === "ltr" || value === "rtl") {
|
|
819
|
+
return value;
|
|
820
|
+
}
|
|
821
|
+
return resolveDirection(element.value, (_b = inherited == null ? void 0 : inherited.value) != null ? _b : "ltr");
|
|
822
|
+
});
|
|
823
|
+
return { dir, isRtl: computed4(() => dir.value === "rtl") };
|
|
824
|
+
}
|
|
573
825
|
export {
|
|
574
826
|
keyInProvides,
|
|
575
827
|
onSmartMounted,
|
|
576
828
|
onSmartUnmounted,
|
|
577
829
|
onWindowResize,
|
|
830
|
+
provideDirection,
|
|
831
|
+
readingDirectionKey,
|
|
832
|
+
resolveDirection,
|
|
833
|
+
useAsyncData,
|
|
578
834
|
useChildren,
|
|
579
835
|
useClickOutside,
|
|
580
836
|
useClientId,
|
|
837
|
+
useDirection,
|
|
581
838
|
useEventListener,
|
|
582
839
|
useId2 as useId,
|
|
583
840
|
useInitialized,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maple-design/use",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "composable utils of maple",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"composable",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"lib"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@maple-design/shared": "1.
|
|
35
|
+
"@maple-design/shared": "1.1.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^18.7.20",
|