@fumari/stf 1.0.1 → 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 +67 -62
- package/dist/index.js +169 -170
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.js +10 -9
- 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.
|
|
@@ -75,10 +24,6 @@ interface DataEngineListener {
|
|
|
75
24
|
* when `field` is specified, this is also fired when parent is deleted.
|
|
76
25
|
*/
|
|
77
26
|
onDelete?: (key: FieldKey, ctx: OnDeleteContext) => void;
|
|
78
|
-
/**
|
|
79
|
-
* For listener attached to a namespace's engine, this is fired when the namespace is removed.
|
|
80
|
-
*/
|
|
81
|
-
onUnmount?: () => void;
|
|
82
27
|
}
|
|
83
28
|
interface OnUpdateContext {
|
|
84
29
|
/**
|
|
@@ -96,9 +41,16 @@ interface OnInitContext {
|
|
|
96
41
|
/** custom data */
|
|
97
42
|
custom?: Record<string, unknown>;
|
|
98
43
|
}
|
|
44
|
+
interface NamespaceConfig {
|
|
45
|
+
reset?: (ctx: {
|
|
46
|
+
engine: DataEngine;
|
|
47
|
+
}) => void;
|
|
48
|
+
}
|
|
99
49
|
declare class DataEngine {
|
|
100
50
|
private data;
|
|
101
|
-
|
|
51
|
+
readonly namespaces: Map<string, {
|
|
52
|
+
engine: DataEngine;
|
|
53
|
+
} & NamespaceConfig>;
|
|
102
54
|
private readonly listeners;
|
|
103
55
|
constructor(defaultValues?: DefaultValue<NonNullable<object>>);
|
|
104
56
|
listen(listener: DataEngineListener): void;
|
|
@@ -106,11 +58,11 @@ declare class DataEngine {
|
|
|
106
58
|
getData(): object;
|
|
107
59
|
/**
|
|
108
60
|
* init a field
|
|
109
|
-
* @param
|
|
61
|
+
* @param field the key of field
|
|
110
62
|
* @param defaultValue the initial value, the field is also created for `undefined`
|
|
111
63
|
* @returns the value of initialized field, or the current value of field if already initialized
|
|
112
64
|
*/
|
|
113
|
-
init(
|
|
65
|
+
init(field: FieldKey, defaultValue?: DefaultValue, ctx?: OnInitContext): unknown;
|
|
114
66
|
delete(key: FieldKey, ctx?: OnDeleteContext): unknown | undefined;
|
|
115
67
|
get(key: FieldKey): unknown;
|
|
116
68
|
/**
|
|
@@ -121,10 +73,63 @@ declare class DataEngine {
|
|
|
121
73
|
/**
|
|
122
74
|
* create an isolated data engine
|
|
123
75
|
*/
|
|
124
|
-
namespace(namespace: string, initialValue?: DefaultValue<NonNullable<object
|
|
76
|
+
namespace(namespace: string, initialValue?: DefaultValue<NonNullable<object>>, config?: NamespaceConfig): DataEngine;
|
|
125
77
|
reset(data: NonNullable<object>): void;
|
|
126
|
-
clearNamespaces(): void;
|
|
127
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
|
+
};
|
|
128
133
|
declare function useFieldValue<V = unknown>(key: FieldKey, options?: {
|
|
129
134
|
stf?: Stf | DataEngine;
|
|
130
135
|
defaultValue?: DefaultValue;
|
|
@@ -145,4 +150,4 @@ declare function useNamespace(options: {
|
|
|
145
150
|
stf?: Stf | DataEngine;
|
|
146
151
|
}): DataEngine;
|
|
147
152
|
//#endregion
|
|
148
|
-
export { ArrayItemInfo, DataEngine, DataEngineListener, DefaultValue, FieldKey, OnDeleteContext, OnInitContext, OnUpdateContext, PropertyItemInfo, Stf, StfProvider, useArray, useDataEngine, useFieldValue, useListener, useNamespace, 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
|
@@ -4,115 +4,6 @@ import { deepEqual, fieldKeyStartsWith, isPlainObject, objectGet, objectSet, str
|
|
|
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;
|
|
@@ -160,10 +51,6 @@ var ListenerManager = class {
|
|
|
160
51
|
for (const v of listeners) v.onDelete?.(field, ctx);
|
|
161
52
|
}
|
|
162
53
|
}
|
|
163
|
-
onUnmount() {
|
|
164
|
-
for (const v of this.unindexed) v.onUnmount?.();
|
|
165
|
-
for (const listeners of this.indexed.values()) for (const v of listeners) v.onUnmount?.();
|
|
166
|
-
}
|
|
167
54
|
};
|
|
168
55
|
var DataEngine = class DataEngine {
|
|
169
56
|
constructor(defaultValues = {}) {
|
|
@@ -182,37 +69,44 @@ var DataEngine = class DataEngine {
|
|
|
182
69
|
}
|
|
183
70
|
/**
|
|
184
71
|
* init a field
|
|
185
|
-
* @param
|
|
72
|
+
* @param field the key of field
|
|
186
73
|
* @param defaultValue the initial value, the field is also created for `undefined`
|
|
187
74
|
* @returns the value of initialized field, or the current value of field if already initialized
|
|
188
75
|
*/
|
|
189
|
-
init(
|
|
190
|
-
if (
|
|
76
|
+
init(field, defaultValue, ctx = {}) {
|
|
77
|
+
if (field.length === 0) return this.data;
|
|
191
78
|
const parentKey = [];
|
|
192
79
|
const parentUpdateCtx = {
|
|
193
80
|
swallow: true,
|
|
194
81
|
...ctx
|
|
195
82
|
};
|
|
196
|
-
let
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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);
|
|
209
99
|
} else {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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);
|
|
215
108
|
this.listeners.onUpdate(parentKey, parentUpdateCtx);
|
|
109
|
+
console.warn(`the original value of field ${parentKey.join(".")} is overidden, this might be unexpected.`);
|
|
216
110
|
}
|
|
217
111
|
}
|
|
218
112
|
}
|
|
@@ -222,16 +116,18 @@ var DataEngine = class DataEngine {
|
|
|
222
116
|
const parentKey = key.slice(0, -1);
|
|
223
117
|
const prop = key[key.length - 1];
|
|
224
118
|
const parent = this.get(parentKey);
|
|
225
|
-
if (
|
|
119
|
+
if (typeof prop === "number" && Array.isArray(parent)) {
|
|
120
|
+
if (parent.length === 0) return;
|
|
121
|
+
const isLast = prop === parent.length - 1;
|
|
226
122
|
const deleted = parent.splice(prop, 1);
|
|
227
123
|
if (deleted.length === 0) return;
|
|
228
124
|
this.listeners.onDelete(key, ctx);
|
|
229
125
|
this.listeners.onUpdate(parentKey, {
|
|
230
|
-
swallow:
|
|
126
|
+
swallow: isLast,
|
|
231
127
|
...ctx
|
|
232
128
|
});
|
|
233
129
|
return deleted[0];
|
|
234
|
-
} else if (isPlainObject(parent)) {
|
|
130
|
+
} else if (isPlainObject(parent) && prop in parent) {
|
|
235
131
|
const temp = parent[prop];
|
|
236
132
|
delete parent[prop];
|
|
237
133
|
this.listeners.onDelete(key, ctx);
|
|
@@ -264,44 +160,156 @@ var DataEngine = class DataEngine {
|
|
|
264
160
|
/**
|
|
265
161
|
* create an isolated data engine
|
|
266
162
|
*/
|
|
267
|
-
namespace(namespace, initialValue) {
|
|
163
|
+
namespace(namespace, initialValue, config) {
|
|
268
164
|
let child = this.namespaces.get(namespace);
|
|
269
165
|
if (!child) {
|
|
270
|
-
child =
|
|
166
|
+
child = {
|
|
167
|
+
engine: new DataEngine(initialValue),
|
|
168
|
+
...config
|
|
169
|
+
};
|
|
271
170
|
this.namespaces.set(namespace, child);
|
|
272
|
-
}
|
|
273
|
-
return child;
|
|
171
|
+
} else Object.assign(child, config);
|
|
172
|
+
return child.engine;
|
|
274
173
|
}
|
|
275
174
|
reset(data) {
|
|
276
175
|
this.update([], data);
|
|
277
|
-
|
|
278
|
-
clearNamespaces() {
|
|
279
|
-
for (const [name, engine] of this.namespaces) {
|
|
280
|
-
this.namespaces.delete(name);
|
|
281
|
-
engine.listeners.onUnmount();
|
|
282
|
-
}
|
|
176
|
+
for (const { engine, reset } of this.namespaces.values()) reset?.({ engine });
|
|
283
177
|
}
|
|
284
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
|
+
}
|
|
285
295
|
function useFieldValue(key, options = {}) {
|
|
286
296
|
const { stf, compute = (v) => v, defaultValue, isChanged = (a, b) => a !== b } = options;
|
|
287
297
|
const engine = useDataEngine(stf);
|
|
288
|
-
const [value, setValue] = useState(() => compute(engine.init(key, defaultValue)));
|
|
298
|
+
const [value, setValue] = useState(() => compute(defaultValue === void 0 ? engine.get(key) : engine.init(key, defaultValue)));
|
|
289
299
|
const prevEngineRef = useRef(engine);
|
|
290
300
|
if (prevEngineRef.current !== engine) {
|
|
291
|
-
setValue(compute(engine.init(key, defaultValue)));
|
|
301
|
+
setValue(compute(defaultValue === void 0 ? engine.get(key) : engine.init(key, defaultValue)));
|
|
292
302
|
prevEngineRef.current = engine;
|
|
293
303
|
}
|
|
304
|
+
function onUpdate() {
|
|
305
|
+
const computed = compute(engine.get(key));
|
|
306
|
+
setValue((prev) => isChanged(prev, computed) ? computed : prev);
|
|
307
|
+
}
|
|
294
308
|
useListener({
|
|
295
309
|
field: key,
|
|
296
310
|
stf,
|
|
297
|
-
onInit
|
|
298
|
-
|
|
299
|
-
setValue((prev) => isChanged(prev, computed) ? computed : prev);
|
|
300
|
-
},
|
|
301
|
-
onUpdate() {
|
|
302
|
-
const computed = compute(engine.get(key));
|
|
303
|
-
setValue((prev) => isChanged(prev, computed) ? computed : prev);
|
|
304
|
-
},
|
|
311
|
+
onInit: onUpdate,
|
|
312
|
+
onUpdate,
|
|
305
313
|
onDelete() {
|
|
306
314
|
const computed = compute(void 0);
|
|
307
315
|
setValue((prev) => isChanged(prev, computed) ? computed : prev);
|
|
@@ -316,9 +324,6 @@ function useListener(listener) {
|
|
|
316
324
|
useEffect(() => {
|
|
317
325
|
const internal = {
|
|
318
326
|
field: listener.field,
|
|
319
|
-
onUnmount(...args) {
|
|
320
|
-
return listenerRef.current.onUnmount?.(...args);
|
|
321
|
-
},
|
|
322
327
|
onDelete(...args) {
|
|
323
328
|
return listenerRef.current.onDelete?.(...args);
|
|
324
329
|
},
|
|
@@ -337,16 +342,10 @@ function useListener(listener) {
|
|
|
337
342
|
}
|
|
338
343
|
function useNamespace(options) {
|
|
339
344
|
const { namespace, stf, initial } = options;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
stf: value,
|
|
344
|
-
onUnmount() {
|
|
345
|
-
setValue(engine.namespace(namespace, initial));
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
return value;
|
|
345
|
+
return useDataEngine(stf).namespace(namespace, initial, { reset({ engine }) {
|
|
346
|
+
engine.update([], getDefaultValue(initial));
|
|
347
|
+
} });
|
|
349
348
|
}
|
|
350
349
|
|
|
351
350
|
//#endregion
|
|
352
|
-
export { DataEngine, StfProvider, useArray, useDataEngine, useFieldValue, useListener, useNamespace, 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
|
*/
|
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
|
/**
|