@arronqzy/vue-rx-store 0.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 ADDED
@@ -0,0 +1,32 @@
1
+ # @arronqzy/vue-rx-store
2
+
3
+ Vue 3 composables,用于订阅 `@arronqzy/rx-store` 全局状态。
4
+
5
+ ## API
6
+
7
+ | Composable | 说明 |
8
+ |------------|------|
9
+ | `useStore(selector?)` | 订阅状态切片(快照值) |
10
+ | `useStoreRef(selector?)` | 返回 `ref`,适合模板 |
11
+ | `useNode(id, selector?)` | 按节点 id 订阅 props |
12
+ | `useSelectedNodes()` | 当前选中节点 props |
13
+ | `useSelectedPositions()` | 选中节点位置 |
14
+ | `useSelectedNodesFull()` | 选中节点完整对象 |
15
+
16
+ ## 示例
17
+
18
+ ```vue
19
+ <script setup lang="ts">
20
+ import { useStoreRef } from "@arronqzy/vue-rx-store";
21
+
22
+ const selectedCount = useStoreRef((s) => s.selectedIds.length);
23
+ </script>
24
+
25
+ <template>
26
+ <span>已选 {{ selectedCount }} 个</span>
27
+ </template>
28
+ ```
29
+
30
+ ## 许可证
31
+
32
+ MIT
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@arronqzy/vue-rx-store",
3
+ "version": "0.1.0",
4
+ "description": "Vue 3 composables for @arronqzy/rx-store",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "files": [
8
+ "src",
9
+ "dist"
10
+ ],
11
+ "main": "./src/index.ts",
12
+ "exports": {
13
+ ".": "./src/index.ts"
14
+ },
15
+ "dependencies": {
16
+ "@arronqzy/rx-store": "1.0.2"
17
+ },
18
+ "peerDependencies": {
19
+ "vue": "^3.4.0"
20
+ },
21
+ "devDependencies": {
22
+ "eslint": "^8.57.0",
23
+ "typescript": "^5.5.4",
24
+ "vue": "^3.5.13",
25
+ "vue-tsc": "^2.2.0",
26
+ "@arronqzy/eslint-config": "0.0.0",
27
+ "@arronqzy/typescript-config": "1.0.0"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "lint": "eslint \"src/**/*.{ts,vue}\"",
34
+ "typecheck": "vue-tsc -p tsconfig.json --noEmit",
35
+ "build": "pnpm run typecheck"
36
+ }
37
+ }
@@ -0,0 +1,13 @@
1
+ import { store } from "@arronqzy/rx-store";
2
+ import { useStoreRef } from "./useStore";
3
+
4
+ export function useNode<T = Record<string, unknown>>(
5
+ id: string,
6
+ selector?: (props: Record<string, unknown>) => T
7
+ ) {
8
+ return useStoreRef((state) => {
9
+ const node = store.findNodeById(state.root, id);
10
+ const props = (node?.props ?? {}) as Record<string, unknown>;
11
+ return selector ? selector(props) : (props as T);
12
+ });
13
+ }
@@ -0,0 +1,36 @@
1
+ import { store, type Node } from "@arronqzy/rx-store";
2
+ import { useStoreRef } from "./useStore";
3
+
4
+ export function useSelectedNodes() {
5
+ return useStoreRef((state) => {
6
+ const selectedMap: Record<string, Record<string, unknown>> = {};
7
+ state.selectedIds.forEach((id) => {
8
+ const node = store.findNodeById(state.root, id);
9
+ if (node) selectedMap[id] = node.props as Record<string, unknown>;
10
+ });
11
+ return selectedMap;
12
+ });
13
+ }
14
+
15
+ export function useSelectedPositions() {
16
+ return useStoreRef((state) => {
17
+ const posMap: Record<string, { x: number; y: number }> = {};
18
+ state.selectedIds.forEach((id) => {
19
+ const node = store.findNodeById(state.root, id);
20
+ if (node) {
21
+ const props = node.props as { x?: number; y?: number };
22
+ posMap[id] = { x: props.x ?? 0, y: props.y ?? 0 };
23
+ }
24
+ });
25
+ return posMap;
26
+ });
27
+ }
28
+
29
+ export function useSelectedNodesFull() {
30
+ return useStoreRef(
31
+ (state) =>
32
+ state.selectedIds
33
+ .map((id) => store.findNodeById(state.root, id))
34
+ .filter(Boolean) as Node[]
35
+ );
36
+ }
@@ -0,0 +1,49 @@
1
+ import { store, type State } from "@arronqzy/rx-store";
2
+ import { ref, onBeforeUnmount } from "vue";
3
+
4
+ function shallowEqual<T>(a: T, b: T): boolean {
5
+ return Object.is(a, b);
6
+ }
7
+
8
+ /**
9
+ * 订阅 rx-store 全局状态切片。
10
+ * @example const count = useStore((s) => s.selectedIds.length);
11
+ */
12
+ export function useStore<T = State>(
13
+ selector?: (state: State) => T,
14
+ equalityFn: (a: T, b: T) => boolean = shallowEqual
15
+ ): T {
16
+ const sel = selector ?? ((s: State) => s as unknown as T);
17
+ const state = ref(sel(store.getState())) as { value: T };
18
+
19
+ const sub = store.select().subscribe(() => {
20
+ const next = sel(store.getState());
21
+ if (!equalityFn(state.value, next)) {
22
+ state.value = next;
23
+ }
24
+ });
25
+
26
+ onBeforeUnmount(() => sub.unsubscribe());
27
+
28
+ return state.value;
29
+ }
30
+
31
+ /** 返回响应式 ref,适合模板绑定 */
32
+ export function useStoreRef<T = State>(
33
+ selector?: (state: State) => T,
34
+ equalityFn: (a: T, b: T) => boolean = shallowEqual
35
+ ) {
36
+ const sel = selector ?? ((s: State) => s as unknown as T);
37
+ const state = ref(sel(store.getState())) as { value: T };
38
+
39
+ const sub = store.select().subscribe(() => {
40
+ const next = sel(store.getState());
41
+ if (!equalityFn(state.value, next)) {
42
+ state.value = next;
43
+ }
44
+ });
45
+
46
+ onBeforeUnmount(() => sub.unsubscribe());
47
+
48
+ return state;
49
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { useStore, useStoreRef } from "./composables/useStore";
2
+ export { useNode } from "./composables/useNode";
3
+ export {
4
+ useSelectedNodes,
5
+ useSelectedPositions,
6
+ useSelectedNodesFull,
7
+ } from "./composables/useSelectedNodes";