tracked-instance 1.0.23 โ 2.0.1
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 +163 -230
- package/dist/index.mjs +21 -12
- package/dist/types/collection.d.ts +28 -3
- package/dist/types/index.d.ts +2 -2
- package/dist/types/tracked-instance.d.ts +18 -2
- package/dist/types/utils.d.ts +36 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -1,285 +1,218 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tracked-instance
|
|
2
|
+
|
|
2
3
|
<a href="https://www.npmjs.com/package/tracked-instance"><img src="https://img.shields.io/npm/v/tracked-instance.svg?sanitize=true" alt="Version"></a>
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
> Track form changes in Vue 3 and send only modified fields to the backend โ no more diffing payloads by hand.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const {data, changedData, isDirty} = useTrackedInstance({name: 'Jack', age: 30})
|
|
9
|
+
|
|
10
|
+
data.value.name = 'John'
|
|
11
|
+
|
|
12
|
+
changedData.value // { name: 'John' } โ only what changed
|
|
13
|
+
isDirty.value // true
|
|
14
|
+
|
|
15
|
+
data.value.name = 'Jack' // revert
|
|
16
|
+
changedData.value // undefined โ back to clean
|
|
17
|
+
isDirty.value // false
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Install
|
|
9
21
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
You can control what data should be sent to the server so that only what has changed is sent.
|
|
14
|
-
Tracked instance is not so much about managing forms, but about building and optimizing queries.
|
|
22
|
+
```bash
|
|
23
|
+
npm i tracked-instance
|
|
24
|
+
```
|
|
15
25
|
|
|
16
|
-
|
|
17
|
-
> npm i tracked-instance
|
|
26
|
+
Supports **Vue 3** only.
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
Supports Vue 3.x only
|
|
28
|
+
---
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
## useTrackedInstance ยท [โถ Try on playground](https://play.vuejs.org/#eNqtVttuGzcQ/ZWpglYyoJuV1AnWstI29kPS1jFivxTQC3eXq2XEJQmSK1mQDeQPArQFigIF+tZvKFCgH+MfaD+hQ+5FK8WXFg1sCEvOkHPOcOaQ69aXSvUXOW0FrbGJNFMWDLW5Ak7E7GjasmbamkwFy5TUFtagaQLXkGiZQRuXtRum3NALTaI5jV8KY4mIaO1pi/keKw24bCrsSlH4Vi4YhSNYTwUAiwMQeRZS7UaWWU4DMFYzMXMTK0p05QBX+MG5X2XO8pAzk1JcHkrJKcHdAWKmaWQlLvGbAwiSbe13PRX4PxWRRFhIICaWdCFKkTmNj/2AmWOm7aoLXJJySlNMEDI7uoXw2NOZdGo2+90Gk3bbjyoanPvhFvyEcEP9dAN9Cbzdhms0Xe85zBWeu0JdpBSOiZ7D14LNUtuMPBoOn30Y2er8zsAvUs2MlSrFvJ9KrIwmkiJ7zJyTBeYV04Il0vE80FwYOTH2nAp7RlYOd+EzfkMjqeNxcR5dyMVcyKWYlCc76bjfRgRDFq5QiFmJCDp7cDQpi6aM3F8QnjsPx8QZdqLW9lfnr0/7imhDO/6zAMCSVadx9IX3HsYHIEvCLAi6hDOsZobrtIuOZXDBMipz29FdeDYcFt71ybhyKre5FafPkS/B8aBoPWw0HFiaKU4sxRHAOGYLLNkVp9iMMTNoWQUJp5eHMCMqGD1R+JVIYXsJyRhfBYYI0zNUs+QQFIljjBjsH6hL38ZFH2zt6fYK9r3V2dCaPp6cxEg4c7U8HuCwWohGTkLKJxeuysahrhehhQmVW1j0MhlT7sA6+r4cpy1wve7UhF5aHFWxlyy2abA/HH6Kk4MNgkERxQUoguyE/w7r+L7o/UIjKhCu7DcYSoH5/yhQGXyj/Js8VE3Vdx310fPxUPRGo29iRymN5qHEwmiEAth4Phw9zK2VAr6IOIvmuKWXRtwvwEIlIacxzn1SSihW2M3Pf/z1+3t447zGg2JxHfkzERp1eOfWrvvv2BmururmatQxwHq9kaXn0C6+bt791gbUtL9//f5PwClaCr5BTcNWrDhvoRsPsGP+Q/tsIJxbbOQdbuhjFBEQVHusIZLcCW5FCME+ogdP6WjkoT4aPSX0YIgAt+iVBOslNz+9RxF1iYo3nHD5zS8/ePGMmwwdKYeiUWTbjR5OGmqI+ZjAzbsfYZkSCzNqDaqfsGAlWLxoUG4WVAf1WqVxriQX4gU50zIXcfAo+dz9NXRp5NQrxFuA6p4mMctNgFJ1CBkTvZS6iys4GHrtQqoNPPAc+XaEtKk729Kw5/iNBxi8yaNSU+wJliCenVthK6NI+hs0F9xU4RA47g2Xu7hRkpBk/0FuBZXdC3GDuzqMrUtguwKr7y2nVrdVPMR6GVH9t0YKfNH5+3FaGvAhV7+EUHl2XmTOOG2l1ioTDAa5UPNZP5LZ4EO/6uWEEa3Bqzlhs514uE4xTvVrZRle3VtxCedy+crP1S8Ov8ap0S3zbw2mzEE7c+KCZTZt1TZLNJZiYT45P/VaWhtRAHN39dxjRCGSPHcYC7ev8CARdsPPo33p04dnemFOLi0VpiJVPTTcW8h543v4xT3UN3Af95/UWbz+B3cQyP8=)
|
|
23
31
|
|
|
24
|
-
|
|
32
|
+
Track changes to a single object, primitive, or array.
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
```js
|
|
35
|
+
import {useTrackedInstance} from 'tracked-instance'
|
|
27
36
|
|
|
28
|
-
```javascript
|
|
29
37
|
const {data, changedData, isDirty, loadData, reset} = useTrackedInstance({
|
|
30
38
|
name: 'Jack',
|
|
31
|
-
isActive: false
|
|
39
|
+
isActive: false,
|
|
32
40
|
})
|
|
33
41
|
```
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
```
|
|
42
|
+
|
|
43
|
+
**Mutate `data.value` directly** โ `changedData` and `isDirty` update automatically:
|
|
44
|
+
|
|
45
|
+
```js
|
|
38
46
|
data.value.name = 'John'
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
isDirty.value // true
|
|
48
|
+
changedData.value // { name: 'John' }
|
|
41
49
|
|
|
50
|
+
// Revert to original value โ field disappears from changedData
|
|
42
51
|
data.value.name = 'Jack'
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
isDirty.value // false
|
|
53
|
+
changedData.value // undefined
|
|
45
54
|
```
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
|
|
56
|
+
**`reset()`** โ revert all changes back to the last loaded baseline:
|
|
57
|
+
|
|
58
|
+
```js
|
|
48
59
|
data.value.name = 'John'
|
|
49
60
|
reset()
|
|
50
|
-
|
|
51
|
-
console.log(isDirty.value) // false
|
|
52
|
-
console.log(changedData.value) // undefined
|
|
61
|
+
data.value // { name: 'Jack', isActive: false }
|
|
53
62
|
```
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
name: 'Joe',
|
|
61
|
-
isActive: false
|
|
62
|
-
})
|
|
63
|
-
console.log(isDirty.value) // false
|
|
64
|
-
console.log(data.value) // { name: 'Joe', isActive: false }
|
|
63
|
+
|
|
64
|
+
**`loadData(newData)`** โ replace data without marking anything dirty (use after a successful save):
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
loadData({name: 'Joe', isActive: true})
|
|
68
|
+
isDirty.value // false โ Joe is now the new baseline
|
|
65
69
|
```
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
Works with primitives and arrays too:
|
|
72
|
+
|
|
73
|
+
```js
|
|
69
74
|
useTrackedInstance(false)
|
|
70
|
-
useTrackedInstance([1,2,3])
|
|
75
|
+
useTrackedInstance([1, 2, 3])
|
|
71
76
|
```
|
|
72
77
|
|
|
73
|
-
###
|
|
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=)
|
|
75
|
-
```vue
|
|
76
|
-
<script setup>
|
|
77
|
-
import {useTrackedInstance} from 'tracked-instance'
|
|
78
|
-
|
|
79
|
-
const {data, changedData, isDirty, reset, loadData} = useTrackedInstance({
|
|
80
|
-
title: '',
|
|
81
|
-
year: null,
|
|
82
|
-
isPublished: false,
|
|
83
|
-
details: {
|
|
84
|
-
// should be updated by loadData
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
// update initial data without make form dirty
|
|
89
|
-
loadData({
|
|
90
|
-
id: 1,
|
|
91
|
-
title: 'The Dark Knight',
|
|
92
|
-
year: 2008,
|
|
93
|
-
isPublished: true,
|
|
94
|
-
details: {
|
|
95
|
-
director: {
|
|
96
|
-
name: 'Christopher Nolan' // form see changes in nested values
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
const saveChanges = () => {
|
|
102
|
-
loadData(data.value)
|
|
103
|
-
}
|
|
104
|
-
</script>
|
|
105
|
-
|
|
106
|
-
<template>
|
|
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>
|
|
136
|
-
</form>
|
|
137
|
-
|
|
138
|
-
<h2>Changed data:</h2>
|
|
139
|
-
<pre>{{ changedData }}</pre>
|
|
140
|
-
</template>
|
|
78
|
+
### Custom equality with `equals`
|
|
141
79
|
|
|
80
|
+
By default values are compared with `===`. Override this for edge cases โ for example when a UI component writes `null`
|
|
81
|
+
but the backend sends `""`:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
const {data, isDirty} = useTrackedInstance(
|
|
85
|
+
{comment: null},
|
|
86
|
+
{equals: (a, b) => (a ?? '') === (b ?? '')}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
data.value.comment = '' // treated as equal to null
|
|
90
|
+
isDirty.value // false
|
|
91
|
+
|
|
92
|
+
data.value.comment = 'hi'
|
|
93
|
+
isDirty.value // true
|
|
142
94
|
```
|
|
143
95
|
|
|
144
|
-
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## useCollection ยท [โถ Try on playground](https://play.vuejs.org/#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)
|
|
99
|
+
|
|
100
|
+
Track an array of items โ add, remove, modify, and reset the whole list.
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
import {useCollection} from 'tracked-instance'
|
|
145
104
|
|
|
146
|
-
|
|
147
|
-
const {isDirty, add, items, remove, reset, loadData} = useCollection()
|
|
105
|
+
const {items, isDirty, add, remove, loadData, reset} = useCollection()
|
|
148
106
|
|
|
149
107
|
loadData([{name: 'Jack'}, {name: 'John'}, {name: 'Joe'}])
|
|
150
108
|
```
|
|
151
|
-
|
|
152
|
-
|
|
109
|
+
|
|
110
|
+
Each item in `items` is a `CollectionItem` with its own `TrackedInstance`:
|
|
111
|
+
|
|
112
|
+
```js
|
|
153
113
|
items.value[0].instance.data.value.name = 'Stepan'
|
|
154
|
-
|
|
155
|
-
```
|
|
156
|
-
Add new item:
|
|
157
|
-
```javascript
|
|
158
|
-
const addedItem = add({name: 'Taras'})
|
|
159
|
-
console.log(addedItem) // {instance: TrackedInstance<{name: 'Taras'}>, isRemoved: false, isNew: true, meta: {}}}
|
|
114
|
+
isDirty.value // true
|
|
160
115
|
```
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
116
|
+
|
|
117
|
+
**`add(item, index?)`** โ add a new item (marked `isNew: true`):
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
const newItem = add({name: 'Taras'})
|
|
121
|
+
// newItem.isNew.value === true
|
|
122
|
+
// newItem.isRemoved.value === false
|
|
123
|
+
|
|
124
|
+
add({name: 'Taras'}, 0) // insert at position 0
|
|
164
125
|
```
|
|
165
126
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
remove(0
|
|
127
|
+
**`remove(index, isHardRemove?)`** โ soft-delete by default, hard-delete with `true`:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
remove(0) // soft remove: isRemoved = true, item stays in array
|
|
131
|
+
remove(0, true) // hard remove: spliced out immediately
|
|
170
132
|
```
|
|
171
133
|
|
|
172
|
-
|
|
173
|
-
|
|
134
|
+
Soft-removed items can be restored with `reset()` or by setting `isRemoved.value = false` manually.
|
|
135
|
+
|
|
136
|
+
**`reset()`** โ removes new items, restores soft-removed ones, reverts all changes:
|
|
137
|
+
|
|
138
|
+
```js
|
|
174
139
|
reset()
|
|
175
140
|
```
|
|
176
141
|
|
|
177
|
-
Item meta
|
|
178
|
-
|
|
179
|
-
|
|
142
|
+
### Item meta
|
|
143
|
+
|
|
144
|
+
Attach computed or reactive metadata to each item via a factory function:
|
|
145
|
+
|
|
146
|
+
```js
|
|
180
147
|
const {add, items} = useCollection(instance => ({
|
|
181
148
|
isValidName: computed(() => instance.data.value.name.length > 0)
|
|
182
149
|
}))
|
|
183
150
|
|
|
184
151
|
add({name: ''})
|
|
152
|
+
items.value[0].meta.isValidName.value // false
|
|
153
|
+
```
|
|
185
154
|
|
|
186
|
-
|
|
155
|
+
The same `options` (including `equals`) are forwarded to every `TrackedInstance` in the collection:
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
const {items} = useCollection(
|
|
159
|
+
() => undefined,
|
|
160
|
+
{equals: (a, b) => (a ?? '') === (b ?? '')}
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## API Reference
|
|
167
|
+
|
|
168
|
+
### useTrackedInstance(initialData?, options?)
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
useTrackedInstance<Data>(initialData ? : Data, options ? : TrackedInstanceOptions)
|
|
172
|
+
:
|
|
173
|
+
TrackedInstance<Data>
|
|
187
174
|
```
|
|
188
175
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
223
|
-
<div>
|
|
224
|
-
Add new user:
|
|
225
|
-
<input
|
|
226
|
-
v-model="newUserName"
|
|
227
|
-
type="text"
|
|
228
|
-
/>
|
|
229
|
-
<button @click="add({name: newUserName}); newUserName = ''">โ Add user</button>
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
|
-
<ul>
|
|
233
|
-
<template v-for="(item, index) in items">
|
|
234
|
-
<li v-if="!item.isRemoved.value">
|
|
235
|
-
<input
|
|
236
|
-
v-model="item.instance.data.value.name"
|
|
237
|
-
type="text"
|
|
238
|
-
/>
|
|
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 }}
|
|
247
|
-
</li>
|
|
248
|
-
</template>
|
|
249
|
-
</ul>
|
|
250
|
-
|
|
251
|
-
Removed items:
|
|
252
|
-
<ul>
|
|
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>
|
|
256
|
-
</li>
|
|
257
|
-
</ul>
|
|
258
|
-
</template>
|
|
176
|
+
| Option | Type | Description |
|
|
177
|
+
|----------|---------------------------------------|------------------------------------------------------------|
|
|
178
|
+
| `equals` | `(a: unknown, b: unknown) => boolean` | Custom equality for primitive leaf values. Replaces `===`. |
|
|
179
|
+
|
|
180
|
+
| Return | Type | Description |
|
|
181
|
+
|---------------------|--------------------------|-------------------------------------------------------------|
|
|
182
|
+
| `data` | `Ref<Data>` | Reactive reference to current data. Mutate directly. |
|
|
183
|
+
| `changedData` | `Ref<DeepPartial<Data>>` | Only modified fields. `undefined` when nothing has changed. |
|
|
184
|
+
| `isDirty` | `Ref<boolean>` | `true` when any field differs from the original. |
|
|
185
|
+
| `loadData(newData)` | `void` | Replace data and clear dirty state (new baseline). |
|
|
186
|
+
| `reset()` | `void` | Revert all changes back to the last `loadData()` baseline. |
|
|
187
|
+
|
|
188
|
+
### useCollection(createItemMeta?, options?)
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
useCollection<Item, Meta>(
|
|
192
|
+
createItemMeta ? : (instance: TrackedInstance<Item>) => Meta,
|
|
193
|
+
options ? : TrackedInstanceOptions,
|
|
194
|
+
)
|
|
195
|
+
:
|
|
196
|
+
Collection<Item, Meta>
|
|
259
197
|
```
|
|
260
198
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
- **isDirty** - weather collection includes some changes (add/remove/change)
|
|
272
|
-
- **add** - add new item
|
|
273
|
-
- **remove** - soft remove item by index. Soft removed items should be deleted permanently after load data. Can be reverted by reset. If passed second param isHardRemove can be deleted permanently.
|
|
274
|
-
- **loadData** - accepts array of data for each item. Rewrite each instance data and clear dirty state
|
|
275
|
-
- **reset** - rollback changes at the last point when the instance was not isDirty
|
|
199
|
+
| Return | Type | Description |
|
|
200
|
+
|--------------------------------|-------------------------|------------------------------------------------------------------|
|
|
201
|
+
| `items` | `Ref<CollectionItem[]>` | Reactive array of collection items. |
|
|
202
|
+
| `isDirty` | `ComputedRef<boolean>` | `true` if any item is dirty, new, or soft-removed. |
|
|
203
|
+
| `add(item, index?)` | `CollectionItem` | Add a new item. Appended to end by default. |
|
|
204
|
+
| `remove(index, isHardRemove?)` | `void` | Soft-remove by default. Pass `true` to splice from array. |
|
|
205
|
+
| `loadData(items)` | `void` | Replace all items and clear dirty state. |
|
|
206
|
+
| `reset()` | `void` | Remove new items, restore soft-removed, reset all instance data. |
|
|
207
|
+
|
|
208
|
+
### CollectionItem
|
|
276
209
|
|
|
277
210
|
```typescript
|
|
278
|
-
interface CollectionItem {
|
|
279
|
-
instance: TrackedInstance
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
meta:
|
|
283
|
-
remove
|
|
211
|
+
interface CollectionItem<Item, Meta = undefined> {
|
|
212
|
+
instance: TrackedInstance<Item> // tracked instance for this item
|
|
213
|
+
isNew: Ref<boolean> // true for items added via add()
|
|
214
|
+
isRemoved: Ref<boolean> // true after soft remove
|
|
215
|
+
meta: Meta // custom metadata from createItemMeta()
|
|
216
|
+
remove(isHardRemove?: boolean): void // shortcut to remove self
|
|
284
217
|
}
|
|
285
218
|
```
|
package/dist/index.mjs
CHANGED
|
@@ -1535,7 +1535,7 @@ var setOriginalDataValue = (originalData, path) => {
|
|
|
1535
1535
|
const lastItem = path.at(-1);
|
|
1536
1536
|
originalDataTarget[lastItem.property] = lastItem.target[lastItem.property];
|
|
1537
1537
|
};
|
|
1538
|
-
var snapshotValueToOriginalData = (originalData, path, value) => {
|
|
1538
|
+
var snapshotValueToOriginalData = (originalData, path, value, equals) => {
|
|
1539
1539
|
const pathAsString = path.map((i) => i.property);
|
|
1540
1540
|
const valueInOriginalData = get_default(originalData, pathAsString);
|
|
1541
1541
|
const markRemovedFieldsAsUndefined = (valueInOriginalData2, oldValue2) => {
|
|
@@ -1555,7 +1555,8 @@ var snapshotValueToOriginalData = (originalData, path, value) => {
|
|
|
1555
1555
|
snapshotValueToOriginalData(
|
|
1556
1556
|
originalData,
|
|
1557
1557
|
path.concat({ target: oldValue2 || value, property: key }),
|
|
1558
|
-
void 0
|
|
1558
|
+
void 0,
|
|
1559
|
+
equals
|
|
1559
1560
|
);
|
|
1560
1561
|
}
|
|
1561
1562
|
};
|
|
@@ -1564,7 +1565,7 @@ var snapshotValueToOriginalData = (originalData, path, value) => {
|
|
|
1564
1565
|
if (isObject2(value) && (isObject2(valueInOriginalData) || isObject2(oldValue))) {
|
|
1565
1566
|
markRemovedFieldsAsUndefined(valueInOriginalData, oldValue);
|
|
1566
1567
|
for (const key of Object.keys(value)) {
|
|
1567
|
-
snapshotValueToOriginalData(originalData, path.concat({ target: oldValue || value, property: key }), value[key]);
|
|
1568
|
+
snapshotValueToOriginalData(originalData, path.concat({ target: oldValue || value, property: key }), value[key], equals);
|
|
1568
1569
|
}
|
|
1569
1570
|
} else if (Array.isArray(value) && (valueInOriginalData instanceof ArrayInOriginalData || Array.isArray(oldValue))) {
|
|
1570
1571
|
markRemovedFieldsAsUndefined(valueInOriginalData, oldValue);
|
|
@@ -1572,20 +1573,24 @@ var snapshotValueToOriginalData = (originalData, path, value) => {
|
|
|
1572
1573
|
snapshotValueToOriginalData(
|
|
1573
1574
|
originalData,
|
|
1574
1575
|
path.concat({ target: oldValue || value, property: key.toString() }),
|
|
1575
|
-
value[key]
|
|
1576
|
+
value[key],
|
|
1577
|
+
equals
|
|
1576
1578
|
);
|
|
1577
1579
|
}
|
|
1578
1580
|
} else {
|
|
1581
|
+
const isEqual = equals ? equals(oldValue, value) : oldValue === value;
|
|
1582
|
+
const isEqualToOriginal = equals ? equals(valueInOriginalData, value) : valueInOriginalData === value;
|
|
1579
1583
|
if (!has_default(originalData, pathAsString)) {
|
|
1580
|
-
if (
|
|
1584
|
+
if (!isEqual) {
|
|
1581
1585
|
setOriginalDataValue(originalData, path);
|
|
1582
1586
|
}
|
|
1583
|
-
} else if (
|
|
1587
|
+
} else if (isEqualToOriginal) {
|
|
1584
1588
|
unset_default(originalData, pathAsString);
|
|
1585
1589
|
}
|
|
1586
1590
|
}
|
|
1587
1591
|
};
|
|
1588
|
-
function useTrackedInstance(initialData) {
|
|
1592
|
+
function useTrackedInstance(initialData, options) {
|
|
1593
|
+
const { equals } = options ?? {};
|
|
1589
1594
|
const _originalData = createNestedRef({}, (path) => ({
|
|
1590
1595
|
deleteProperty(target, property) {
|
|
1591
1596
|
const result = Reflect.deleteProperty(target, property);
|
|
@@ -1623,13 +1628,13 @@ function useTrackedInstance(initialData) {
|
|
|
1623
1628
|
triggerChangingArrayItems();
|
|
1624
1629
|
}
|
|
1625
1630
|
} else {
|
|
1626
|
-
snapshotValueToOriginalData(_originalData.value, path, value);
|
|
1631
|
+
snapshotValueToOriginalData(_originalData.value, path, value, equals);
|
|
1627
1632
|
}
|
|
1628
1633
|
return Reflect.set(target, property, cloneDeep(value), receiver);
|
|
1629
1634
|
},
|
|
1630
1635
|
deleteProperty(target, property) {
|
|
1631
1636
|
const path = parentThree.concat({ target, property });
|
|
1632
|
-
snapshotValueToOriginalData(_originalData.value, path, void 0);
|
|
1637
|
+
snapshotValueToOriginalData(_originalData.value, path, void 0, equals);
|
|
1633
1638
|
return Reflect.deleteProperty(target, property);
|
|
1634
1639
|
}
|
|
1635
1640
|
}));
|
|
@@ -1660,7 +1665,7 @@ function useTrackedInstance(initialData) {
|
|
|
1660
1665
|
_originalData.value = {};
|
|
1661
1666
|
};
|
|
1662
1667
|
const reset = () => {
|
|
1663
|
-
const updatedData =
|
|
1668
|
+
const updatedData = cloneDeep(_data.value);
|
|
1664
1669
|
for (const [path, value] of iterateObject(_originalData.value, { includeParent: true })) {
|
|
1665
1670
|
if (value instanceof ArrayInOriginalData) {
|
|
1666
1671
|
set_default(updatedData, path.concat("length"), value.length);
|
|
@@ -1686,7 +1691,11 @@ function useTrackedInstance(initialData) {
|
|
|
1686
1691
|
|
|
1687
1692
|
// src/collection.ts
|
|
1688
1693
|
import { computed as computed2, markRaw, ref } from "vue";
|
|
1689
|
-
var useCollection = (
|
|
1694
|
+
var useCollection = (options) => {
|
|
1695
|
+
const {
|
|
1696
|
+
createItemMeta = () => void 0,
|
|
1697
|
+
...instanceOptions
|
|
1698
|
+
} = options ?? {};
|
|
1690
1699
|
const items = ref([]);
|
|
1691
1700
|
const isDirty = computed2(
|
|
1692
1701
|
() => items.value.some(({
|
|
@@ -1696,7 +1705,7 @@ var useCollection = (createItemMeta = () => void 0) => {
|
|
|
1696
1705
|
}) => instance.isDirty.value || isNew.value || isRemoved.value)
|
|
1697
1706
|
);
|
|
1698
1707
|
const createItem = (item, isNew) => {
|
|
1699
|
-
const instance = useTrackedInstance(item);
|
|
1708
|
+
const instance = useTrackedInstance(item, instanceOptions);
|
|
1700
1709
|
const collectionItem = markRaw({
|
|
1701
1710
|
isRemoved: ref(false),
|
|
1702
1711
|
isNew: ref(isNew),
|
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
import { ComputedRef, Raw, Ref } from 'vue';
|
|
2
|
-
import { TrackedInstance } from './tracked-instance';
|
|
2
|
+
import { TrackedInstance, TrackedInstanceOptions } from './tracked-instance';
|
|
3
3
|
export type CollectionItem<Item, Meta = undefined> = Raw<{
|
|
4
4
|
instance: TrackedInstance<Item>;
|
|
5
|
+
/** Arbitrary metadata attached to this item, produced by CollectionOptions.createItemMeta. */
|
|
5
6
|
meta: Meta;
|
|
7
|
+
/** True when the item has been soft-deleted via remove(). */
|
|
6
8
|
isRemoved: Ref<boolean>;
|
|
9
|
+
/** True for items added via add() after the last loadData() call. */
|
|
7
10
|
isNew: Ref<boolean>;
|
|
11
|
+
/** Removes this item from the collection. Shortcut for calling collection.remove(index). */
|
|
8
12
|
remove: (isHardRemoved?: boolean) => void;
|
|
9
13
|
}>;
|
|
10
14
|
export interface Collection<Item, Meta = undefined> {
|
|
11
15
|
items: Ref<CollectionItem<Item, Meta>[]>;
|
|
16
|
+
/** True when any item is modified, newly added, or soft-deleted. */
|
|
12
17
|
isDirty: ComputedRef<boolean>;
|
|
13
|
-
|
|
18
|
+
/** Adds an item to the collection. Inserts at the end by default; pass `index` to insert elsewhere. */
|
|
19
|
+
add: (item: Item, index?: number) => CollectionItem<Item, Meta>;
|
|
20
|
+
/** Soft-deletes an item by index (sets isRemoved). Pass isHardRemove=true to splice immediately. */
|
|
14
21
|
remove: (index: number, isHardRemove?: boolean) => void;
|
|
22
|
+
/** Replaces all items and clears the dirty state. The loaded items become the new baseline. */
|
|
15
23
|
loadData: (items: Item[]) => void;
|
|
24
|
+
/** Reverts all changes: drops new items, restores removed items, resets modified fields. */
|
|
16
25
|
reset: () => void;
|
|
17
26
|
}
|
|
18
|
-
export
|
|
27
|
+
export interface CollectionOptions<Item, Meta = undefined> extends TrackedInstanceOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Factory called when a collection item is created (via loadData or add).
|
|
30
|
+
* Use it to attach arbitrary metadata to each item โ UI flags, sub-forms, derived state โ
|
|
31
|
+
* that lives alongside the tracked instance but is not part of the tracked data.
|
|
32
|
+
* Receives the newly created TrackedInstance so the meta can reference reactive instance fields.
|
|
33
|
+
*/
|
|
34
|
+
createItemMeta?: (instance: TrackedInstance<Item>) => Meta;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Creates a reactive collection of TrackedInstance items.
|
|
38
|
+
*
|
|
39
|
+
* Tracks additions, removals, and field-level modifications across all items.
|
|
40
|
+
* Each item is wrapped with markRaw to prevent Vue from making the collection item
|
|
41
|
+
* itself deeply reactive โ only instance.data, isRemoved, and isNew carry reactivity.
|
|
42
|
+
*/
|
|
43
|
+
export declare const useCollection: <Item = any, Meta = undefined>(options?: CollectionOptions<Item, Meta>) => Collection<Item, Meta>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { TrackedInstance } from './tracked-instance';
|
|
2
|
-
export type { Collection, CollectionItem } from './collection';
|
|
1
|
+
export type { TrackedInstance, TrackedInstanceOptions } from './tracked-instance';
|
|
2
|
+
export type { Collection, CollectionItem, CollectionOptions } from './collection';
|
|
3
3
|
export { useTrackedInstance } from './tracked-instance';
|
|
4
4
|
export { useCollection } from './collection';
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { Ref } from 'vue';
|
|
2
2
|
import { DeepPartial } from './utils';
|
|
3
3
|
export interface TrackedInstance<Data> {
|
|
4
|
+
/** Reactive reference to the current (possibly modified) data. */
|
|
4
5
|
data: Ref<Data>;
|
|
6
|
+
/** True when at least one field differs from the value at the last loadData() call. */
|
|
5
7
|
isDirty: Ref<boolean>;
|
|
8
|
+
/** Partial object containing only the fields that have changed since the last loadData(). */
|
|
6
9
|
changedData: Ref<DeepPartial<Data>>;
|
|
10
|
+
/** Replaces the current data and clears the dirty state. The new value becomes the new baseline. */
|
|
7
11
|
loadData: (newData: Data) => void;
|
|
12
|
+
/** Reverts all changes, restoring data to the state at the last loadData() call. */
|
|
8
13
|
reset: () => void;
|
|
9
14
|
}
|
|
10
|
-
export
|
|
11
|
-
|
|
15
|
+
export interface TrackedInstanceOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Custom equality function for comparing primitive values.
|
|
18
|
+
* When provided, replaces the default strict equality (===) check.
|
|
19
|
+
* Called only for primitive leaf values (strings, numbers, booleans, null, undefined).
|
|
20
|
+
*
|
|
21
|
+
* @example treat null and empty string as equal
|
|
22
|
+
* equals: (a, b) => (a ?? '') === (b ?? '')
|
|
23
|
+
*/
|
|
24
|
+
equals?: (a: unknown, b: unknown) => boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function useTrackedInstance<Data = any>(value?: undefined, options?: TrackedInstanceOptions): TrackedInstance<Data | undefined>;
|
|
27
|
+
export declare function useTrackedInstance<Data>(value: Data, options?: TrackedInstanceOptions): TrackedInstance<Data>;
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -1,16 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively makes all properties of T optional.
|
|
3
|
+
* Arrays use element-level DeepPartial rather than making the array itself optional,
|
|
4
|
+
* which allows sparse array diffs (e.g. only index 2 changed).
|
|
5
|
+
*/
|
|
1
6
|
export type DeepPartial<Value> = Value extends object ? Value extends Array<infer ArrayValue> ? Array<DeepPartial<ArrayValue>> : {
|
|
2
7
|
[Property in keyof Value]?: DeepPartial<Value[Property]>;
|
|
3
8
|
} : Value;
|
|
9
|
+
/**
|
|
10
|
+
* Represents one segment in the path from the root proxy to the currently accessed node.
|
|
11
|
+
* Accumulated as Proxy `get` traps are traversed, then passed to `set`/`deleteProperty`
|
|
12
|
+
* handlers so they can reconstruct the full property path for _originalData bookkeeping.
|
|
13
|
+
*/
|
|
4
14
|
export interface NestedProxyPathItem {
|
|
5
15
|
target: Record<string, any>;
|
|
6
16
|
property: string;
|
|
7
17
|
receiver?: Record<string, any>;
|
|
8
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns true only for plain objects โ intentionally excludes Array, Date, File, Map,
|
|
21
|
+
* and Set so they are treated as atomic leaf values rather than being traversed.
|
|
22
|
+
*/
|
|
9
23
|
export declare const isObject: (value: unknown) => boolean;
|
|
10
24
|
export declare const isEmpty: (value: object) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Depth-first generator that walks an object tree, yielding [path, value] pairs.
|
|
27
|
+
*
|
|
28
|
+
* By default it descends into plain objects (via isObject). Supply `goDeepCondition`
|
|
29
|
+
* to override โ e.g. to also descend into ArrayInOriginalData entries.
|
|
30
|
+
* When `includeParent` is true, intermediate nodes are yielded before their children,
|
|
31
|
+
* which is needed for reset() to handle ArrayInOriginalData length restoration.
|
|
32
|
+
*/
|
|
11
33
|
export declare const iterateObject: (source: Record<string, any>, params?: {
|
|
12
34
|
goDeepCondition?: (path: string[], value: any) => boolean;
|
|
13
35
|
includeParent?: boolean;
|
|
14
36
|
}) => Generator<[string[], any], void, any>;
|
|
37
|
+
/**
|
|
38
|
+
* Creates a Vue customRef whose value is a deeply nested Proxy tree.
|
|
39
|
+
*
|
|
40
|
+
* Every nested object/array returned by a `get` is itself wrapped in a new Proxy,
|
|
41
|
+
* so mutations at any depth trigger Vue's reactivity system via the root `track`/`trigger`
|
|
42
|
+
* pair. The `handler` factory receives the full path from the root to the current node,
|
|
43
|
+
* allowing callers to intercept `set` and `deleteProperty` with complete path context.
|
|
44
|
+
*/
|
|
15
45
|
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, Source>;
|
|
46
|
+
/**
|
|
47
|
+
* Deep-clones a value while preserving special types:
|
|
48
|
+
* - Date โ new Date instance with the same timestamp
|
|
49
|
+
* - File โ same reference (Files are immutable browser objects and cannot be meaningfully cloned)
|
|
50
|
+
* All other types delegate to lodash cloneDeepWith for recursive cloning.
|
|
51
|
+
*/
|
|
16
52
|
export declare const cloneDeep: (inputValue: any) => any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tracked-instance",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Build large forms and track all changes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
},
|
|
11
11
|
"main": "./dist/index.mjs",
|
|
12
12
|
"types": "./dist/types/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.mjs",
|
|
16
|
+
"types": "./dist/types/index.d.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
13
19
|
"files": [
|
|
14
20
|
"dist"
|
|
15
21
|
],
|
|
@@ -28,7 +34,7 @@
|
|
|
28
34
|
},
|
|
29
35
|
"repository": {
|
|
30
36
|
"type": "git",
|
|
31
|
-
"url": "git+https://github.com/rudnik275/tracked-instance"
|
|
37
|
+
"url": "git+https://github.com/rudnik275/tracked-instance.git"
|
|
32
38
|
},
|
|
33
39
|
"author": "Dmytro Rudnyk",
|
|
34
40
|
"license": "MIT",
|