tracked-instance 1.0.11 → 1.0.13
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 +95 -30
- package/dist/index.mjs +10 -5
- package/dist/types/collection.d.ts +17 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/tracked-instance.d.ts +11 -0
- package/dist/types/utils.d.ts +15 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -71,6 +71,7 @@ useTrackedInstance([1,2,3])
|
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
### Real-world example
|
|
74
|
+
[Try on playground](https://play.vuejs.org/#eNqNVc1u00AQfpXBl7RSapfCAVlJVGh7KEiloj36srEn8TbrtbU/aaMoz4DEjROvgYTEw/AC8AjM7tqpaarSm+d/vplv1uvobdPES4tRGo10rnhjQKOxzSSTvGpqZWBtNV4rli+wOJfaMJnjBmaqrmBggvqAt/pBJjOZ1yTBumCGDSEvmZxjceoFrk+5MqshKKQaQxA185YNjGG3yN46kwCGG4EpDAZDJ62QqRSkFcKLXF/aqeC6xCKFGRMavbpAw7jQKfgMAEkCuqytKGCKYBvqDOlzta3vvDaZ3Oy79sk5uACX3HAmwCGBW24ohYGKLRBmtaqgcFgy2SUJ7XJq5KVvomv8ukQ4ZWoBHySfl6aP4+jw8M0uDqPs4zCoIOampsBWASBZ5WqclIprUzclKrioBZMDh9l3qRHbJWgCBBK1A79kwqIOWQh5D37YnmZLPGmjxrC3D+NJKLpF64YS+zQURtGjJLCHeEOCwaoRNEOSAEYFX07a3VPv644HsNmMEmfzTqWCJHxNrTG1hONc8HwxziLPliya/Pr64/f3z/DJiaMkePlyFOOxHms7rbiJG4VLlIZCezgoQcA74rKxppvg8qCqCxTk6xH5rWVRZzWrBslk8I4aaMn0VJpY2mqKqsvm1ryTLLg8K12Xp8ePnXR5ifliWt/1Ez4jZUuuuCNV7Kj0X+Bt4jD7B75h+Pcp0oJrNhVYkO1Fu/LO2oIG+PPty0+4oi11JG0r3K/XCW673abLo0nYaOEPMx0lpPEWWvuE2NV7cjzDnNrxs8fJaBiFx+2gYk18o2tJz5/nd9YaiC7bM6MxPHjnnDGLSmManSaJlc1iHud1lez6dbdFFY2m25rx+YN6FNdwgepjYzjd3j91mRD17Xuv274KPsbt/BH9jSYauNYu3c2oJTWwtRmm5u6MnPns6sIvdmskaljH+yeMdHa1sK7H4PbOyoLa7vn5bs/9+LicX+uzO4NSd6Bco34a3j+L6J9z8gT0+3Zfxa+3U9z8BYQrOQM=)
|
|
74
75
|
```vue
|
|
75
76
|
<script setup>
|
|
76
77
|
import {useTrackedInstance} from 'tracked-instance'
|
|
@@ -78,28 +79,66 @@ useTrackedInstance([1,2,3])
|
|
|
78
79
|
const {data, changedData, isDirty, reset, loadData} = useTrackedInstance({
|
|
79
80
|
title: '',
|
|
80
81
|
year: null,
|
|
81
|
-
isPublished: false
|
|
82
|
+
isPublished: false,
|
|
83
|
+
details: {
|
|
84
|
+
// should be updated by loadData
|
|
85
|
+
}
|
|
82
86
|
})
|
|
83
87
|
|
|
88
|
+
// update initial data without make form dirty
|
|
84
89
|
loadData({
|
|
85
90
|
id: 1,
|
|
86
91
|
title: 'The Dark Knight',
|
|
87
92
|
year: 2008,
|
|
88
|
-
isPublished: true
|
|
93
|
+
isPublished: true,
|
|
94
|
+
details: {
|
|
95
|
+
director: {
|
|
96
|
+
name: 'Christopher Nolan' // form see changes in nested values
|
|
97
|
+
}
|
|
98
|
+
}
|
|
89
99
|
})
|
|
100
|
+
|
|
101
|
+
const saveChanges = () => {
|
|
102
|
+
loadData(data.value)
|
|
103
|
+
}
|
|
90
104
|
</script>
|
|
91
105
|
|
|
92
106
|
<template>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<input
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
<div>isDirty: {{ isDirty }}</div>
|
|
108
|
+
<hr />
|
|
109
|
+
<button @click="reset">♻️ Reset</button>
|
|
110
|
+
|
|
111
|
+
<form @submit.prevent="saveChanges">
|
|
112
|
+
<input
|
|
113
|
+
v-model="data.title"
|
|
114
|
+
type="text"
|
|
115
|
+
/>
|
|
116
|
+
<input
|
|
117
|
+
v-model.number="data.year"
|
|
118
|
+
type="number"
|
|
119
|
+
/>
|
|
120
|
+
<input
|
|
121
|
+
v-model="data.isPublished"
|
|
122
|
+
type="checkbox"
|
|
123
|
+
/>
|
|
124
|
+
|
|
125
|
+
<input
|
|
126
|
+
v-model="data.details.director.name"
|
|
127
|
+
type="text"
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
<button
|
|
131
|
+
type="submit"
|
|
132
|
+
:disabled="!isDirty"
|
|
133
|
+
>
|
|
134
|
+
💾 Save changes
|
|
135
|
+
</button>
|
|
101
136
|
</form>
|
|
137
|
+
|
|
138
|
+
<h2>Changed data:</h2>
|
|
139
|
+
<pre>{{ changedData }}</pre>
|
|
102
140
|
</template>
|
|
141
|
+
|
|
103
142
|
```
|
|
104
143
|
|
|
105
144
|
## Collection
|
|
@@ -148,46 +187,72 @@ console.log(items.value[0].meta.isValidName.value) // false
|
|
|
148
187
|
```
|
|
149
188
|
|
|
150
189
|
### Real-world example
|
|
190
|
+
[Try on playground](https://play.vuejs.org/#eNp9VcFy0zAQ/ZWtL3ZnUvsAp5BmKG0P9FCYFk6Yg2sriRpZ9khymk7G38DADMNwob/BDDN8DD9AP4GVZDuym3BJLO1q973dp9XGOynLcFURb+xNZCpoqUASVZXTmNO8LISCjSCzGmaiyMFHR39rqCQ5LRgjqaIFb12USNIlyY4olyrhqfaPeVrgCjZUnlGh7keQZNkIqCK5HIEgebEi+h8Tj4AVSXaWqKSGY+glCA51pNYcfNjwJCdj8C8wn1+PoFsXC95fE7/+aA5bGJzcvZdEXKIZcyC7wPcds0xW5HSR8DmRaA4O4XgKm5hDhyzQC7Dww1XCKmI3AMIZZYqIQJv0sQP9EVJ5ZShm1hlTNd55UgbG1+Swvk3VwgwTOf74U8d8EtkWYXNwgf4lSxTBFcDkplKqwG0deZxRmdwwkh3H3kFT9Niztpcpo+kSDQ5PazNxAB4fvvyGazRC2lh1+MjGd3NtQ5nWxd70z/dff39+giu97J9YCIgMavzO6GragBrDZgPNN9T1JNI2x80iOsky3TQtBzG2WxPKy0q1lVwd5UVGGCJxetsSBlD3JUGbImsEaTc1GBNnSAWVGTTKcWLVhy8GsvF9zffHVwNOA+vzdYlUrE3WdgwBzwqB2Uz38SbwjKwP8c+KCiO30CeMojOdmUbuEJPjOixKvzD71BVqrttS7SmXW7KdZbOXOLBEENTjw7fPqAO92StM73gfaY/kJblrCbpeXb4+HaM/nA+Or5MNwNWlg+IpMJPYinKAA9XZ9SRitO1o1L+Ekem1/my6ZBtqROvowDTVKsCMirbx7fwIqJ0IT0fHtt0dxj09dQEPu7VLSajpWcKkVlRbLhy9NzhcB3Xq6DdsezXwRp59HY5wuIW3suD4sJjpiVmNAdWNBbahUGSD10IbY2+hVCnHUVTxcjkP0yKPnvrpCMiwxoxK4uSe0fkgH54rKSPiTamfj37ehLHi7sLsKVGRUbufLki63LF/K9cW2lutNbFCAJ1NJWKux582n19fmmvTGfH+VayhtceIsixYpTFat1cVzxC242fQvjblo3z+Tp6vFeGyJaWBmmoY/9jDR/r0P9S3cJ+Fz7sq1v8AiAKmmg==)
|
|
151
191
|
```vue
|
|
152
192
|
<script setup>
|
|
153
|
-
import {ref} from 'vue'
|
|
154
|
-
import {useCollection} from 'tracked-instance'
|
|
193
|
+
import {ref} from 'vue'
|
|
194
|
+
import {useCollection} from 'tracked-instance'
|
|
155
195
|
|
|
156
|
-
const {isDirty, add, items, remove, reset, loadData} = useCollection()
|
|
196
|
+
const {isDirty, add, items, remove, reset, loadData} = useCollection()
|
|
157
197
|
|
|
158
|
-
loadData([{name: 'Jack'}, {name: 'John'}, {name: 'Joe'}])
|
|
198
|
+
loadData([{name: 'Jack'}, {name: 'John'}, {name: 'Joe'}])
|
|
199
|
+
|
|
200
|
+
const newUserName = ref('')
|
|
159
201
|
|
|
160
|
-
const
|
|
202
|
+
const saveChanges = () => {
|
|
203
|
+
loadData(
|
|
204
|
+
items.value
|
|
205
|
+
.filter(item => !item.isRemoved.value)
|
|
206
|
+
.map((item) => item.instance.data.value)
|
|
207
|
+
)
|
|
208
|
+
}
|
|
161
209
|
</script>
|
|
162
210
|
|
|
163
211
|
<template>
|
|
212
|
+
<button
|
|
213
|
+
:disabled="!isDirty"
|
|
214
|
+
@click="saveChanges"
|
|
215
|
+
>
|
|
216
|
+
💾 Save changes
|
|
217
|
+
</button>
|
|
218
|
+
<button @click="reset">♻️ Reset</button>
|
|
219
|
+
<hr />
|
|
220
|
+
|
|
221
|
+
<div>isDirty: {{ isDirty }}</div>
|
|
222
|
+
|
|
164
223
|
<div>
|
|
165
|
-
|
|
224
|
+
Add new user:
|
|
225
|
+
<input
|
|
226
|
+
v-model="newUserName"
|
|
227
|
+
type="text"
|
|
228
|
+
/>
|
|
229
|
+
<button @click="add({name: newUserName}); newUserName = ''">➕ Add user</button>
|
|
166
230
|
</div>
|
|
167
231
|
|
|
168
|
-
<button @click="reset">Reset</button>
|
|
169
|
-
|
|
170
|
-
<div>
|
|
171
|
-
Add new user:
|
|
172
|
-
<input v-model="newUserName" type="text">
|
|
173
|
-
<button @click="add({name: newUserName})">➕ Add user</button>
|
|
174
|
-
</div>
|
|
175
|
-
|
|
176
232
|
<ul>
|
|
177
233
|
<template v-for="(item, index) in items">
|
|
178
|
-
<li v-if="!item.isRemoved">
|
|
179
|
-
<input
|
|
180
|
-
|
|
234
|
+
<li v-if="!item.isRemoved.value">
|
|
235
|
+
<input
|
|
236
|
+
v-model="item.instance.data.value.name"
|
|
237
|
+
type="text"
|
|
238
|
+
/>
|
|
181
239
|
<button @click="remove(index)">🗑 Remove</button>
|
|
240
|
+
<button
|
|
241
|
+
v-if="!item.isNew.value"
|
|
242
|
+
@click="item.instance.reset()"
|
|
243
|
+
>
|
|
244
|
+
♻️ Reset
|
|
245
|
+
</button>
|
|
246
|
+
isNew: {{ item.isNew.value }}
|
|
182
247
|
</li>
|
|
183
248
|
</template>
|
|
184
249
|
</ul>
|
|
185
250
|
|
|
186
251
|
Removed items:
|
|
187
252
|
<ul>
|
|
188
|
-
<li v-for="item in items.filter()">
|
|
189
|
-
{{item.instance.data.name}}
|
|
190
|
-
<button @click="item.isRemoved = false">♻️ Rollback</button>
|
|
253
|
+
<li v-for="item in items.filter((i) => i.isRemoved.value)">
|
|
254
|
+
{{ item.instance.data.value.name }}
|
|
255
|
+
<button @click="item.isRemoved.value = false">♻️ Rollback</button>
|
|
191
256
|
</li>
|
|
192
257
|
</ul>
|
|
193
258
|
</template>
|
package/dist/index.mjs
CHANGED
|
@@ -708,7 +708,10 @@ function unset(object, path) {
|
|
|
708
708
|
var unset_default = unset;
|
|
709
709
|
|
|
710
710
|
// src/tracked-instance.ts
|
|
711
|
-
import { computed
|
|
711
|
+
import { computed } from "vue";
|
|
712
|
+
|
|
713
|
+
// src/utils.ts
|
|
714
|
+
import { customRef } from "vue";
|
|
712
715
|
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof File) && !(value instanceof Map) && !(value instanceof Set);
|
|
713
716
|
var isEmpty = (value) => Object.keys(value).length === 0;
|
|
714
717
|
var iterateObject = function* (source, params = {}) {
|
|
@@ -765,6 +768,8 @@ var createNestedRef = (source, handler) => customRef((track, trigger) => {
|
|
|
765
768
|
}
|
|
766
769
|
};
|
|
767
770
|
});
|
|
771
|
+
|
|
772
|
+
// src/tracked-instance.ts
|
|
768
773
|
var ArrayInOriginalData = class {
|
|
769
774
|
length;
|
|
770
775
|
constructor(length) {
|
|
@@ -845,7 +850,7 @@ var snapshotValueToOriginalData = (originalData, path, value) => {
|
|
|
845
850
|
}
|
|
846
851
|
}
|
|
847
852
|
};
|
|
848
|
-
|
|
853
|
+
function useTrackedInstance(initialData) {
|
|
849
854
|
const _originalData = createNestedRef({}, (path) => ({
|
|
850
855
|
deleteProperty(target, property) {
|
|
851
856
|
const result = Reflect.deleteProperty(target, property);
|
|
@@ -941,11 +946,11 @@ var useTrackedInstance = (initialData) => {
|
|
|
941
946
|
loadData,
|
|
942
947
|
reset
|
|
943
948
|
};
|
|
944
|
-
}
|
|
949
|
+
}
|
|
945
950
|
|
|
946
951
|
// src/collection.ts
|
|
947
|
-
import { computed as computed2, shallowRef, triggerRef
|
|
948
|
-
var useCollection = (createItemMeta = () =>
|
|
952
|
+
import { computed as computed2, ref, shallowRef, triggerRef } from "vue";
|
|
953
|
+
var useCollection = (createItemMeta = () => void 0) => {
|
|
949
954
|
const items = shallowRef([]);
|
|
950
955
|
const isDirty = computed2(
|
|
951
956
|
() => items.value.some(({ instance, isRemoved, isNew }) => instance.isDirty.value || isNew.value || isRemoved.value)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ComputedRef, Ref, ShallowRef } from 'vue';
|
|
2
|
+
import { TrackedInstance } from './tracked-instance';
|
|
3
|
+
export interface CollectionItem<Item, Meta> {
|
|
4
|
+
instance: TrackedInstance<Item>;
|
|
5
|
+
meta: Meta;
|
|
6
|
+
isRemoved: Ref<boolean>;
|
|
7
|
+
isNew: Ref<boolean>;
|
|
8
|
+
}
|
|
9
|
+
export interface Collection<Item, Meta> {
|
|
10
|
+
items: ShallowRef<CollectionItem<Item, Meta>[]>;
|
|
11
|
+
isDirty: ComputedRef<boolean>;
|
|
12
|
+
add: (item: Item, afterIndex?: number) => CollectionItem<Item, Meta>;
|
|
13
|
+
remove: (index: number, isHardRemove?: boolean) => void;
|
|
14
|
+
loadData: (items: Item[]) => void;
|
|
15
|
+
reset: () => void;
|
|
16
|
+
}
|
|
17
|
+
export declare const useCollection: <Item = any, Meta = any>(createItemMeta?: (instance: TrackedInstance<Item>) => Meta) => Collection<Item, Meta>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
import { DeepPartial } from './utils';
|
|
3
|
+
export interface TrackedInstance<Data> {
|
|
4
|
+
data: Ref<Data>;
|
|
5
|
+
isDirty: Ref<boolean>;
|
|
6
|
+
changedData: Ref<DeepPartial<Data>>;
|
|
7
|
+
loadData: (newData: Data) => void;
|
|
8
|
+
reset: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function useTrackedInstance<Data = any>(): TrackedInstance<Data | undefined>;
|
|
11
|
+
export declare function useTrackedInstance<Data>(value: Data): TrackedInstance<Data>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type DeepPartial<T> = T extends object ? {
|
|
2
|
+
[P in keyof T]?: DeepPartial<T[P]>;
|
|
3
|
+
} : T;
|
|
4
|
+
export interface NestedProxyPathItem {
|
|
5
|
+
target: Record<string, any>;
|
|
6
|
+
property: string;
|
|
7
|
+
receiver?: Record<string, any>;
|
|
8
|
+
}
|
|
9
|
+
export declare const isObject: (value: unknown) => boolean;
|
|
10
|
+
export declare const isEmpty: (value: object) => boolean;
|
|
11
|
+
export declare const iterateObject: (source: Record<string, any>, params?: {
|
|
12
|
+
goDeepCondition?: ((path: string[], value: any) => boolean) | undefined;
|
|
13
|
+
includeParent?: boolean | undefined;
|
|
14
|
+
}) => Generator<[string[], any], void, unknown>;
|
|
15
|
+
export declare const createNestedRef: <Source extends Record<string, any>>(source: Source, handler: <InnerSource extends Record<string, any>>(path: NestedProxyPathItem[]) => ProxyHandler<InnerSource>) => import("vue").Ref<Source>;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tracked-instance",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Build large forms and track all changes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "esbuild ./src/index.ts --bundle --external:vue --outfile=./dist/index.mjs --format=esm",
|
|
7
|
+
"build": "tsc && esbuild ./src/index.ts --bundle --external:vue --outfile=./dist/index.mjs --format=esm",
|
|
8
8
|
"test": "vitest run"
|
|
9
9
|
},
|
|
10
10
|
"main": "./dist/index.mjs",
|