@fumari/stf 1.0.0 → 1.0.2
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 +73 -59
- package/dist/index.js +184 -151
- package/dist/lib/utils.d.ts +3 -2
- package/dist/lib/utils.js +19 -12
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,60 +2,9 @@ import { t as FieldKey } from "./types-Do_aTV5I.js";
|
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
|
-
//#region src/lib/stf.d.ts
|
|
6
|
-
interface Stf {
|
|
7
|
-
dataEngine: DataEngine;
|
|
8
|
-
}
|
|
9
|
-
declare function StfProvider({
|
|
10
|
-
value,
|
|
11
|
-
children
|
|
12
|
-
}: {
|
|
13
|
-
value: Stf;
|
|
14
|
-
children: ReactNode;
|
|
15
|
-
}): react_jsx_runtime0.JSX.Element;
|
|
16
|
-
declare function useStf(options: {
|
|
17
|
-
/**
|
|
18
|
-
* Note: the passed object will be modified in place, use `structuredClone()` to keep the original object unchanged.
|
|
19
|
-
*/
|
|
20
|
-
defaultValues?: DefaultValue<Record<string, unknown>>;
|
|
21
|
-
}): Stf;
|
|
22
|
-
declare function useDataEngine(instance?: Stf | DataEngine): DataEngine;
|
|
23
|
-
interface ArrayItemInfo {
|
|
24
|
-
field: FieldKey;
|
|
25
|
-
index: number;
|
|
26
|
-
}
|
|
27
|
-
declare function useArray(field: FieldKey, options?: {
|
|
28
|
-
defaultValue?: DefaultValue<unknown[]>;
|
|
29
|
-
}): {
|
|
30
|
-
items: ArrayItemInfo[];
|
|
31
|
-
insertItem(itemValue?: unknown): void;
|
|
32
|
-
removeItem(index: number): void;
|
|
33
|
-
};
|
|
34
|
-
type PropertyItemInfo<T> = {
|
|
35
|
-
kind: 'fixed' | 'fallback';
|
|
36
|
-
field: FieldKey;
|
|
37
|
-
key: string;
|
|
38
|
-
info: T;
|
|
39
|
-
} | {
|
|
40
|
-
kind: 'pattern';
|
|
41
|
-
field: FieldKey;
|
|
42
|
-
key: string;
|
|
43
|
-
pattern: string;
|
|
44
|
-
info: T;
|
|
45
|
-
};
|
|
46
|
-
declare function useObject<T>(field: FieldKey, options: {
|
|
47
|
-
defaultValue?: DefaultValue<object>;
|
|
48
|
-
properties: Record<string, T>;
|
|
49
|
-
patternProperties?: Record<string, T>;
|
|
50
|
-
fallback?: T;
|
|
51
|
-
}): {
|
|
52
|
-
properties: PropertyItemInfo<T>[];
|
|
53
|
-
onAppend(name: string, value?: unknown): void;
|
|
54
|
-
onDelete(name: string): unknown;
|
|
55
|
-
};
|
|
56
|
-
//#endregion
|
|
57
5
|
//#region src/lib/data-engine.d.ts
|
|
58
6
|
type DefaultValue<T = unknown> = T | (() => T);
|
|
7
|
+
declare function getDefaultValue<T>(defaultValue: DefaultValue<T>): T;
|
|
59
8
|
interface DataEngineListener {
|
|
60
9
|
/**
|
|
61
10
|
* when specified, only call the listener for events affecting the specified field.
|
|
@@ -92,10 +41,16 @@ interface OnInitContext {
|
|
|
92
41
|
/** custom data */
|
|
93
42
|
custom?: Record<string, unknown>;
|
|
94
43
|
}
|
|
44
|
+
interface NamespaceConfig {
|
|
45
|
+
reset?: (ctx: {
|
|
46
|
+
engine: DataEngine;
|
|
47
|
+
}) => void;
|
|
48
|
+
}
|
|
95
49
|
declare class DataEngine {
|
|
96
50
|
private data;
|
|
97
|
-
|
|
98
|
-
|
|
51
|
+
readonly namespaces: Map<string, {
|
|
52
|
+
engine: DataEngine;
|
|
53
|
+
} & NamespaceConfig>;
|
|
99
54
|
private readonly listeners;
|
|
100
55
|
constructor(defaultValues?: DefaultValue<NonNullable<object>>);
|
|
101
56
|
listen(listener: DataEngineListener): void;
|
|
@@ -103,11 +58,11 @@ declare class DataEngine {
|
|
|
103
58
|
getData(): object;
|
|
104
59
|
/**
|
|
105
60
|
* init a field
|
|
106
|
-
* @param
|
|
61
|
+
* @param field the key of field
|
|
107
62
|
* @param defaultValue the initial value, the field is also created for `undefined`
|
|
108
63
|
* @returns the value of initialized field, or the current value of field if already initialized
|
|
109
64
|
*/
|
|
110
|
-
init(
|
|
65
|
+
init(field: FieldKey, defaultValue?: DefaultValue, ctx?: OnInitContext): unknown;
|
|
111
66
|
delete(key: FieldKey, ctx?: OnDeleteContext): unknown | undefined;
|
|
112
67
|
get(key: FieldKey): unknown;
|
|
113
68
|
/**
|
|
@@ -118,9 +73,63 @@ declare class DataEngine {
|
|
|
118
73
|
/**
|
|
119
74
|
* create an isolated data engine
|
|
120
75
|
*/
|
|
121
|
-
namespace(namespace: string, initialValue?: DefaultValue<NonNullable<object
|
|
122
|
-
reset(data:
|
|
76
|
+
namespace(namespace: string, initialValue?: DefaultValue<NonNullable<object>>, config?: NamespaceConfig): DataEngine;
|
|
77
|
+
reset(data: NonNullable<object>): void;
|
|
123
78
|
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/lib/stf.d.ts
|
|
81
|
+
interface Stf {
|
|
82
|
+
dataEngine: DataEngine;
|
|
83
|
+
}
|
|
84
|
+
declare function StfProvider({
|
|
85
|
+
value,
|
|
86
|
+
children
|
|
87
|
+
}: {
|
|
88
|
+
value: Stf;
|
|
89
|
+
children: ReactNode;
|
|
90
|
+
}): react_jsx_runtime0.JSX.Element;
|
|
91
|
+
declare function useStf(options: {
|
|
92
|
+
/**
|
|
93
|
+
* Note: the passed object will be modified in place, use `structuredClone()` to keep the original object unchanged.
|
|
94
|
+
*/
|
|
95
|
+
defaultValues?: DefaultValue<Record<string, unknown>>;
|
|
96
|
+
}): Stf;
|
|
97
|
+
declare function useDataEngine(instance?: Stf | DataEngine): DataEngine;
|
|
98
|
+
interface ArrayItemInfo {
|
|
99
|
+
field: FieldKey;
|
|
100
|
+
index: number;
|
|
101
|
+
}
|
|
102
|
+
declare function useArray(field: FieldKey, options?: {
|
|
103
|
+
defaultValue?: DefaultValue<unknown[]>;
|
|
104
|
+
}): {
|
|
105
|
+
items: ArrayItemInfo[];
|
|
106
|
+
insertItem(itemValue?: unknown): void;
|
|
107
|
+
removeItem(index: number): void;
|
|
108
|
+
};
|
|
109
|
+
type PropertyItemInfo<T> = {
|
|
110
|
+
kind: 'fixed' | 'fallback';
|
|
111
|
+
field: FieldKey;
|
|
112
|
+
key: string;
|
|
113
|
+
info: T;
|
|
114
|
+
} | {
|
|
115
|
+
kind: 'pattern';
|
|
116
|
+
field: FieldKey;
|
|
117
|
+
key: string;
|
|
118
|
+
pattern: string;
|
|
119
|
+
info: T;
|
|
120
|
+
};
|
|
121
|
+
declare function useObject<T>(field: FieldKey, options: {
|
|
122
|
+
defaultValue?: DefaultValue<object>; /** ignore fixed properties unless defined */
|
|
123
|
+
lazy?: boolean;
|
|
124
|
+
properties: Record<string, T>;
|
|
125
|
+
patternProperties?: Record<string, T>;
|
|
126
|
+
fallback?: T;
|
|
127
|
+
}): {
|
|
128
|
+
properties: PropertyItemInfo<T>[];
|
|
129
|
+
_objectKeys: string[];
|
|
130
|
+
onAppend(name: string, value?: unknown): void;
|
|
131
|
+
onDelete(name: string): unknown;
|
|
132
|
+
};
|
|
124
133
|
declare function useFieldValue<V = unknown>(key: FieldKey, options?: {
|
|
125
134
|
stf?: Stf | DataEngine;
|
|
126
135
|
defaultValue?: DefaultValue;
|
|
@@ -135,5 +144,10 @@ declare function useFieldValue<V = unknown>(key: FieldKey, options?: {
|
|
|
135
144
|
declare function useListener(listener: DataEngineListener & {
|
|
136
145
|
stf?: Stf | DataEngine;
|
|
137
146
|
}): void;
|
|
147
|
+
declare function useNamespace(options: {
|
|
148
|
+
namespace: string;
|
|
149
|
+
initial?: DefaultValue<NonNullable<object>>;
|
|
150
|
+
stf?: Stf | DataEngine;
|
|
151
|
+
}): DataEngine;
|
|
138
152
|
//#endregion
|
|
139
|
-
export { ArrayItemInfo, DataEngine, DataEngineListener, DefaultValue, FieldKey, OnDeleteContext, OnInitContext, OnUpdateContext, PropertyItemInfo, Stf, StfProvider, useArray, useDataEngine, useFieldValue, useListener, useObject, useStf };
|
|
153
|
+
export { ArrayItemInfo, DataEngine, DataEngineListener, DefaultValue, FieldKey, NamespaceConfig, OnDeleteContext, OnInitContext, OnUpdateContext, PropertyItemInfo, Stf, StfProvider, getDefaultValue, useArray, useDataEngine, useFieldValue, useListener, useNamespace, useObject, useStf };
|
package/dist/index.js
CHANGED
|
@@ -1,118 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { deepEqual, fieldKeyStartsWith, objectGet, objectSet, stringifyFieldKey } from "./lib/utils.js";
|
|
3
|
+
import { deepEqual, fieldKeyStartsWith, isPlainObject, objectGet, objectSet, stringifyFieldKey } from "./lib/utils.js";
|
|
4
4
|
import { createContext, use, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
|
|
7
|
-
//#region src/lib/stf.tsx
|
|
8
|
-
const Context = createContext(null);
|
|
9
|
-
function StfProvider({ value, children }) {
|
|
10
|
-
return /* @__PURE__ */ jsx(Context, {
|
|
11
|
-
value,
|
|
12
|
-
children
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
function useStf(options) {
|
|
16
|
-
const { defaultValues } = options;
|
|
17
|
-
const dataEngine = useMemo(() => new DataEngine(defaultValues), []);
|
|
18
|
-
return useMemo(() => ({ dataEngine }), [dataEngine]);
|
|
19
|
-
}
|
|
20
|
-
function useDataEngine(instance) {
|
|
21
|
-
if (instance instanceof DataEngine) return instance;
|
|
22
|
-
if (instance) return instance.dataEngine;
|
|
23
|
-
return use(Context).dataEngine;
|
|
24
|
-
}
|
|
25
|
-
function useArray(field, options = {}) {
|
|
26
|
-
const engine = useDataEngine();
|
|
27
|
-
const [items] = useFieldValue(field, {
|
|
28
|
-
defaultValue: options.defaultValue,
|
|
29
|
-
compute(value) {
|
|
30
|
-
const items = [];
|
|
31
|
-
if (Array.isArray(value)) for (let i = 0; i < value.length; i++) items.push({
|
|
32
|
-
field: [...field, i],
|
|
33
|
-
index: i
|
|
34
|
-
});
|
|
35
|
-
return items;
|
|
36
|
-
},
|
|
37
|
-
isChanged(prev, next) {
|
|
38
|
-
return prev.length !== next.length;
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
return {
|
|
42
|
-
items,
|
|
43
|
-
insertItem(itemValue) {
|
|
44
|
-
const value = engine.get(field);
|
|
45
|
-
engine.update(field, Array.isArray(value) ? [...value, itemValue] : [itemValue]);
|
|
46
|
-
},
|
|
47
|
-
removeItem(index) {
|
|
48
|
-
engine.delete([...field, index]);
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function useObject(field, options) {
|
|
53
|
-
const engine = useDataEngine();
|
|
54
|
-
const [objectKeys] = useFieldValue(field, {
|
|
55
|
-
defaultValue: options.defaultValue,
|
|
56
|
-
compute(currentValue) {
|
|
57
|
-
return currentValue ? Object.keys(currentValue) : [];
|
|
58
|
-
},
|
|
59
|
-
isChanged(prev, next) {
|
|
60
|
-
return !deepEqual(prev, next);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
return {
|
|
64
|
-
properties: useMemo(() => {
|
|
65
|
-
const properties = [];
|
|
66
|
-
const unknownKeys = new Set(objectKeys);
|
|
67
|
-
for (const [key, prop] of Object.entries(options.properties)) {
|
|
68
|
-
unknownKeys.delete(key);
|
|
69
|
-
properties.push({
|
|
70
|
-
kind: "fixed",
|
|
71
|
-
field: [...field, key],
|
|
72
|
-
key,
|
|
73
|
-
info: prop
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
for (const [pattern, prop] of Object.entries(options.patternProperties ?? {})) {
|
|
77
|
-
const regex = RegExp(pattern);
|
|
78
|
-
for (const key of unknownKeys) {
|
|
79
|
-
if (!key.match(regex)) continue;
|
|
80
|
-
unknownKeys.delete(key);
|
|
81
|
-
properties.push({
|
|
82
|
-
kind: "pattern",
|
|
83
|
-
info: prop,
|
|
84
|
-
key,
|
|
85
|
-
pattern,
|
|
86
|
-
field: [...field, key]
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (options.fallback) for (const key of unknownKeys) properties.push({
|
|
91
|
-
kind: "fallback",
|
|
92
|
-
field: [...field, key],
|
|
93
|
-
key,
|
|
94
|
-
info: options.fallback
|
|
95
|
-
});
|
|
96
|
-
return properties;
|
|
97
|
-
}, [
|
|
98
|
-
field,
|
|
99
|
-
objectKeys,
|
|
100
|
-
options.fallback,
|
|
101
|
-
options.patternProperties,
|
|
102
|
-
options.properties
|
|
103
|
-
]),
|
|
104
|
-
onAppend(name, value) {
|
|
105
|
-
name = name.trim();
|
|
106
|
-
if (name.length === 0) return;
|
|
107
|
-
engine.init([...field, name], value);
|
|
108
|
-
},
|
|
109
|
-
onDelete(name) {
|
|
110
|
-
return engine.delete([...field, name]);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
//#endregion
|
|
116
7
|
//#region src/lib/data-engine.ts
|
|
117
8
|
function getDefaultValue(defaultValue) {
|
|
118
9
|
return typeof defaultValue === "function" ? defaultValue() : defaultValue;
|
|
@@ -178,34 +69,46 @@ var DataEngine = class DataEngine {
|
|
|
178
69
|
}
|
|
179
70
|
/**
|
|
180
71
|
* init a field
|
|
181
|
-
* @param
|
|
72
|
+
* @param field the key of field
|
|
182
73
|
* @param defaultValue the initial value, the field is also created for `undefined`
|
|
183
74
|
* @returns the value of initialized field, or the current value of field if already initialized
|
|
184
75
|
*/
|
|
185
|
-
init(
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
const currentKey = [];
|
|
76
|
+
init(field, defaultValue, ctx = {}) {
|
|
77
|
+
if (field.length === 0) return this.data;
|
|
78
|
+
const parentKey = [];
|
|
189
79
|
const parentUpdateCtx = {
|
|
190
80
|
swallow: true,
|
|
191
81
|
...ctx
|
|
192
82
|
};
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
83
|
+
let parent = this.data;
|
|
84
|
+
const fieldsToInit = [];
|
|
85
|
+
let initStart = null;
|
|
86
|
+
for (let i = 0; i < field.length; i++) {
|
|
87
|
+
const key = field[i];
|
|
88
|
+
const value = parent[key];
|
|
89
|
+
if (i === field.length - 1) {
|
|
90
|
+
if (value !== void 0) return value;
|
|
91
|
+
const out = parent[key] = getDefaultValue(defaultValue);
|
|
92
|
+
fieldsToInit.push(field);
|
|
93
|
+
for (const initField of fieldsToInit) this.listeners.onInit(initField, ctx);
|
|
94
|
+
this.listeners.onUpdate(initStart ?? parentKey, parentUpdateCtx);
|
|
95
|
+
return out;
|
|
96
|
+
} else if (isPlainObject(value) || Array.isArray(value)) {
|
|
97
|
+
parent = value;
|
|
98
|
+
parentKey.push(key);
|
|
99
|
+
} else {
|
|
100
|
+
const nextKey = field[i + 1];
|
|
101
|
+
parent = parent[key] = typeof nextKey === "number" ? new Array(nextKey + 1) : {};
|
|
102
|
+
if (value === void 0) {
|
|
103
|
+
initStart ??= [...parentKey];
|
|
104
|
+
parentKey.push(key);
|
|
105
|
+
fieldsToInit.push([...parentKey]);
|
|
106
|
+
} else {
|
|
107
|
+
parentKey.push(key);
|
|
108
|
+
this.listeners.onUpdate(parentKey, parentUpdateCtx);
|
|
109
|
+
console.warn(`the original value of field ${parentKey.join(".")} is overidden, this might be unexpected.`);
|
|
110
|
+
}
|
|
207
111
|
}
|
|
208
|
-
currentKey.push(propKey);
|
|
209
112
|
}
|
|
210
113
|
}
|
|
211
114
|
delete(key, ctx = {}) {
|
|
@@ -213,16 +116,18 @@ var DataEngine = class DataEngine {
|
|
|
213
116
|
const parentKey = key.slice(0, -1);
|
|
214
117
|
const prop = key[key.length - 1];
|
|
215
118
|
const parent = this.get(parentKey);
|
|
216
|
-
if (
|
|
119
|
+
if (typeof prop === "number" && Array.isArray(parent)) {
|
|
120
|
+
if (parent.length === 0) return;
|
|
121
|
+
const isLast = prop === parent.length - 1;
|
|
217
122
|
const deleted = parent.splice(prop, 1);
|
|
218
123
|
if (deleted.length === 0) return;
|
|
219
124
|
this.listeners.onDelete(key, ctx);
|
|
220
125
|
this.listeners.onUpdate(parentKey, {
|
|
221
|
-
swallow:
|
|
126
|
+
swallow: isLast,
|
|
222
127
|
...ctx
|
|
223
128
|
});
|
|
224
129
|
return deleted[0];
|
|
225
|
-
} else if (
|
|
130
|
+
} else if (isPlainObject(parent) && prop in parent) {
|
|
226
131
|
const temp = parent[prop];
|
|
227
132
|
delete parent[prop];
|
|
228
133
|
this.listeners.onDelete(key, ctx);
|
|
@@ -255,34 +160,156 @@ var DataEngine = class DataEngine {
|
|
|
255
160
|
/**
|
|
256
161
|
* create an isolated data engine
|
|
257
162
|
*/
|
|
258
|
-
namespace(namespace, initialValue) {
|
|
163
|
+
namespace(namespace, initialValue, config) {
|
|
259
164
|
let child = this.namespaces.get(namespace);
|
|
260
165
|
if (!child) {
|
|
261
|
-
child =
|
|
166
|
+
child = {
|
|
167
|
+
engine: new DataEngine(initialValue),
|
|
168
|
+
...config
|
|
169
|
+
};
|
|
262
170
|
this.namespaces.set(namespace, child);
|
|
263
|
-
}
|
|
264
|
-
return child;
|
|
171
|
+
} else Object.assign(child, config);
|
|
172
|
+
return child.engine;
|
|
265
173
|
}
|
|
266
174
|
reset(data) {
|
|
267
|
-
this.namespaces.clear();
|
|
268
175
|
this.update([], data);
|
|
176
|
+
for (const { engine, reset } of this.namespaces.values()) reset?.({ engine });
|
|
269
177
|
}
|
|
270
178
|
};
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/lib/stf.tsx
|
|
182
|
+
const Context = createContext(null);
|
|
183
|
+
function StfProvider({ value, children }) {
|
|
184
|
+
return /* @__PURE__ */ jsx(Context, {
|
|
185
|
+
value,
|
|
186
|
+
children
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function useStf(options) {
|
|
190
|
+
const optionsRef = useRef(options);
|
|
191
|
+
optionsRef.current = options;
|
|
192
|
+
return useMemo(() => ({ dataEngine: new DataEngine(optionsRef.current.defaultValues) }), []);
|
|
193
|
+
}
|
|
194
|
+
function useDataEngine(instance) {
|
|
195
|
+
if (instance instanceof DataEngine) return instance;
|
|
196
|
+
if (instance) return instance.dataEngine;
|
|
197
|
+
return use(Context).dataEngine;
|
|
198
|
+
}
|
|
199
|
+
function useArray(field, options = {}) {
|
|
200
|
+
const engine = useDataEngine();
|
|
201
|
+
const { defaultValue } = options;
|
|
202
|
+
const [items] = useFieldValue(field, {
|
|
203
|
+
defaultValue,
|
|
204
|
+
compute(value) {
|
|
205
|
+
const items = [];
|
|
206
|
+
if (!Array.isArray(value)) return items;
|
|
207
|
+
for (let i = 0; i < value.length; i++) items.push({
|
|
208
|
+
field: [...field, i],
|
|
209
|
+
index: i
|
|
210
|
+
});
|
|
211
|
+
return items;
|
|
212
|
+
},
|
|
213
|
+
isChanged(prev, next) {
|
|
214
|
+
return prev.length !== next.length;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
items,
|
|
219
|
+
insertItem(itemValue) {
|
|
220
|
+
const value = engine.get(field);
|
|
221
|
+
const idx = Array.isArray(value) ? value.length : 0;
|
|
222
|
+
engine.init([...field, idx], itemValue);
|
|
223
|
+
},
|
|
224
|
+
removeItem(index) {
|
|
225
|
+
engine.delete([...field, index]);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function useObject(field, options) {
|
|
230
|
+
const { properties: definedProps, patternProperties: definedPatternProps = {}, defaultValue, fallback, lazy } = options;
|
|
231
|
+
const engine = useDataEngine();
|
|
232
|
+
const [objectKeys] = useFieldValue(field, {
|
|
233
|
+
defaultValue,
|
|
234
|
+
compute(currentValue) {
|
|
235
|
+
return isPlainObject(currentValue) ? Object.keys(currentValue) : [];
|
|
236
|
+
},
|
|
237
|
+
isChanged(prev, next) {
|
|
238
|
+
return !deepEqual(prev, next);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
properties: useMemo(() => {
|
|
243
|
+
const properties = [];
|
|
244
|
+
const unknownKeys = new Set(objectKeys);
|
|
245
|
+
for (const [key, prop] of Object.entries(definedProps)) {
|
|
246
|
+
if (lazy && !unknownKeys.has(key)) continue;
|
|
247
|
+
unknownKeys.delete(key);
|
|
248
|
+
properties.push({
|
|
249
|
+
kind: "fixed",
|
|
250
|
+
field: [...field, key],
|
|
251
|
+
key,
|
|
252
|
+
info: prop
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
for (const [pattern, prop] of Object.entries(definedPatternProps)) {
|
|
256
|
+
const regex = RegExp(pattern);
|
|
257
|
+
for (const key of unknownKeys) {
|
|
258
|
+
if (!key.match(regex)) continue;
|
|
259
|
+
unknownKeys.delete(key);
|
|
260
|
+
properties.push({
|
|
261
|
+
kind: "pattern",
|
|
262
|
+
info: prop,
|
|
263
|
+
key,
|
|
264
|
+
pattern,
|
|
265
|
+
field: [...field, key]
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (fallback) for (const key of unknownKeys) properties.push({
|
|
270
|
+
kind: "fallback",
|
|
271
|
+
field: [...field, key],
|
|
272
|
+
key,
|
|
273
|
+
info: fallback
|
|
274
|
+
});
|
|
275
|
+
return properties;
|
|
276
|
+
}, [
|
|
277
|
+
definedPatternProps,
|
|
278
|
+
definedProps,
|
|
279
|
+
fallback,
|
|
280
|
+
field,
|
|
281
|
+
lazy,
|
|
282
|
+
objectKeys
|
|
283
|
+
]),
|
|
284
|
+
_objectKeys: objectKeys,
|
|
285
|
+
onAppend(name, value) {
|
|
286
|
+
name = name.trim();
|
|
287
|
+
if (name.length === 0) return;
|
|
288
|
+
engine.init([...field, name], value);
|
|
289
|
+
},
|
|
290
|
+
onDelete(name) {
|
|
291
|
+
return engine.delete([...field, name]);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
271
295
|
function useFieldValue(key, options = {}) {
|
|
272
|
-
const
|
|
273
|
-
const
|
|
274
|
-
const [value, setValue] = useState(() => compute(engine.init(key, defaultValue)));
|
|
296
|
+
const { stf, compute = (v) => v, defaultValue, isChanged = (a, b) => a !== b } = options;
|
|
297
|
+
const engine = useDataEngine(stf);
|
|
298
|
+
const [value, setValue] = useState(() => compute(defaultValue === void 0 ? engine.get(key) : engine.init(key, defaultValue)));
|
|
299
|
+
const prevEngineRef = useRef(engine);
|
|
300
|
+
if (prevEngineRef.current !== engine) {
|
|
301
|
+
setValue(compute(defaultValue === void 0 ? engine.get(key) : engine.init(key, defaultValue)));
|
|
302
|
+
prevEngineRef.current = engine;
|
|
303
|
+
}
|
|
304
|
+
function onUpdate() {
|
|
305
|
+
const computed = compute(engine.get(key));
|
|
306
|
+
setValue((prev) => isChanged(prev, computed) ? computed : prev);
|
|
307
|
+
}
|
|
275
308
|
useListener({
|
|
276
309
|
field: key,
|
|
277
|
-
stf
|
|
278
|
-
onInit
|
|
279
|
-
|
|
280
|
-
setValue((prev) => isChanged(prev, computed) ? computed : prev);
|
|
281
|
-
},
|
|
282
|
-
onUpdate() {
|
|
283
|
-
const computed = compute(engine.get(key));
|
|
284
|
-
setValue((prev) => isChanged(prev, computed) ? computed : prev);
|
|
285
|
-
},
|
|
310
|
+
stf,
|
|
311
|
+
onInit: onUpdate,
|
|
312
|
+
onUpdate,
|
|
286
313
|
onDelete() {
|
|
287
314
|
const computed = compute(void 0);
|
|
288
315
|
setValue((prev) => isChanged(prev, computed) ? computed : prev);
|
|
@@ -313,6 +340,12 @@ function useListener(listener) {
|
|
|
313
340
|
};
|
|
314
341
|
}, [engine, listener.field]);
|
|
315
342
|
}
|
|
343
|
+
function useNamespace(options) {
|
|
344
|
+
const { namespace, stf, initial } = options;
|
|
345
|
+
return useDataEngine(stf).namespace(namespace, initial, { reset({ engine }) {
|
|
346
|
+
engine.update([], getDefaultValue(initial));
|
|
347
|
+
} });
|
|
348
|
+
}
|
|
316
349
|
|
|
317
350
|
//#endregion
|
|
318
|
-
export { DataEngine, StfProvider, useArray, useDataEngine, useFieldValue, useListener, useObject, useStf };
|
|
351
|
+
export { DataEngine, StfProvider, getDefaultValue, useArray, useDataEngine, useFieldValue, useListener, useNamespace, useObject, useStf };
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ declare function objectGet(obj: unknown, key: (string | number)[]): unknown | un
|
|
|
7
7
|
*
|
|
8
8
|
* @returns updated value, throw error if parent object doesn't exist
|
|
9
9
|
*/
|
|
10
|
-
declare function objectSet(obj: unknown,
|
|
10
|
+
declare function objectSet(obj: unknown, field: FieldKey, value: unknown): unknown;
|
|
11
11
|
/**
|
|
12
12
|
* doesn't handle recursive objects
|
|
13
13
|
*/
|
|
@@ -17,5 +17,6 @@ declare function stringifyFieldKey(fieldKey: FieldKey): string;
|
|
|
17
17
|
* @returns if `a` starts with `b`.
|
|
18
18
|
*/
|
|
19
19
|
declare function fieldKeyStartsWith(a: string, b: string): boolean;
|
|
20
|
+
declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
20
21
|
//#endregion
|
|
21
|
-
export { deepEqual, fieldKeyStartsWith, objectGet, objectSet, stringifyFieldKey };
|
|
22
|
+
export { deepEqual, fieldKeyStartsWith, isPlainObject, objectGet, objectSet, stringifyFieldKey };
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
//#region src/lib/utils.ts
|
|
2
2
|
function objectGet(obj, key) {
|
|
3
3
|
let cur = obj;
|
|
4
|
-
for (const prop of key)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
4
|
+
for (const prop of key) if (isPlainObject(cur) && prop in cur) cur = cur[prop];
|
|
5
|
+
else if (typeof prop === "number" && Array.isArray(cur)) cur = cur[prop];
|
|
6
|
+
else return;
|
|
8
7
|
return cur;
|
|
9
8
|
}
|
|
10
9
|
/**
|
|
@@ -12,11 +11,13 @@ function objectGet(obj, key) {
|
|
|
12
11
|
*
|
|
13
12
|
* @returns updated value, throw error if parent object doesn't exist
|
|
14
13
|
*/
|
|
15
|
-
function objectSet(obj,
|
|
16
|
-
if (
|
|
17
|
-
const parent = objectGet(obj,
|
|
18
|
-
|
|
19
|
-
parent[key
|
|
14
|
+
function objectSet(obj, field, value) {
|
|
15
|
+
if (field.length === 0) return value;
|
|
16
|
+
const parent = objectGet(obj, field.slice(0, -1));
|
|
17
|
+
const key = field[field.length - 1];
|
|
18
|
+
if (isPlainObject(parent)) parent[key] = value;
|
|
19
|
+
else if (typeof key === "number" && Array.isArray(parent)) parent[key] = value;
|
|
20
|
+
else throw new Error("missing parent object");
|
|
20
21
|
return obj;
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
@@ -31,13 +32,14 @@ function deepEqual(a, b) {
|
|
|
31
32
|
return a.every((item, index) => deepEqual(item, b[index]));
|
|
32
33
|
}
|
|
33
34
|
if (Array.isArray(a) || Array.isArray(b)) return false;
|
|
35
|
+
if (!isPlainObject(a) || !isPlainObject(b)) return false;
|
|
34
36
|
const keysA = Object.keys(a);
|
|
35
37
|
const keysB = Object.keys(b);
|
|
36
38
|
if (keysA.length !== keysB.length) return false;
|
|
37
|
-
return keysA.every((key) =>
|
|
39
|
+
return keysA.every((key) => key in b && deepEqual(a[key], b[key]));
|
|
38
40
|
}
|
|
39
41
|
function stringifyFieldKey(fieldKey) {
|
|
40
|
-
return fieldKey.map((v) =>
|
|
42
|
+
return fieldKey.map((v) => typeof v === "string" ? `_${v}` : `n${v}`).join(".");
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* @returns if `a` starts with `b`.
|
|
@@ -45,6 +47,11 @@ function stringifyFieldKey(fieldKey) {
|
|
|
45
47
|
function fieldKeyStartsWith(a, b) {
|
|
46
48
|
return b.length === 0 || a === b || a.startsWith(b + ".");
|
|
47
49
|
}
|
|
50
|
+
function isPlainObject(value) {
|
|
51
|
+
if (typeof value !== "object" || value === null) return false;
|
|
52
|
+
const prototype = Object.getPrototypeOf(value);
|
|
53
|
+
return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
|
|
54
|
+
}
|
|
48
55
|
|
|
49
56
|
//#endregion
|
|
50
|
-
export { deepEqual, fieldKeyStartsWith, objectGet, objectSet, stringifyFieldKey };
|
|
57
|
+
export { deepEqual, fieldKeyStartsWith, isPlainObject, objectGet, objectSet, stringifyFieldKey };
|