tracked-instance 2.0.0 โ 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 +143 -265
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,340 +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
|
-
### Custom equality with `equals`
|
|
78
|
+
### Custom equality with `equals`
|
|
74
79
|
|
|
75
|
-
By default
|
|
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 `""`:
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{ comment: null },
|
|
82
|
-
{ equals: (a, b) => (a ?? '') === (b ?? '') }
|
|
83
|
+
```js
|
|
84
|
+
const {data, isDirty} = useTrackedInstance(
|
|
85
|
+
{comment: null},
|
|
86
|
+
{equals: (a, b) => (a ?? '') === (b ?? '')}
|
|
83
87
|
)
|
|
84
88
|
|
|
85
|
-
data.value.comment = ''
|
|
86
|
-
|
|
89
|
+
data.value.comment = '' // treated as equal to null
|
|
90
|
+
isDirty.value // false
|
|
87
91
|
|
|
88
|
-
data.value.comment = '
|
|
89
|
-
|
|
92
|
+
data.value.comment = 'hi'
|
|
93
|
+
isDirty.value // true
|
|
90
94
|
```
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
### Real-world example
|
|
95
|
-
[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=)
|
|
96
|
-
```vue
|
|
97
|
-
<script setup>
|
|
98
|
-
import {useTrackedInstance} from 'tracked-instance'
|
|
99
|
-
|
|
100
|
-
const {data, changedData, isDirty, reset, loadData} = useTrackedInstance({
|
|
101
|
-
title: '',
|
|
102
|
-
year: null,
|
|
103
|
-
isPublished: false,
|
|
104
|
-
details: {
|
|
105
|
-
// should be updated by loadData
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// update initial data without make form dirty
|
|
110
|
-
loadData({
|
|
111
|
-
id: 1,
|
|
112
|
-
title: 'The Dark Knight',
|
|
113
|
-
year: 2008,
|
|
114
|
-
isPublished: true,
|
|
115
|
-
details: {
|
|
116
|
-
director: {
|
|
117
|
-
name: 'Christopher Nolan' // form see changes in nested values
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
const saveChanges = () => {
|
|
123
|
-
loadData(data.value)
|
|
124
|
-
}
|
|
125
|
-
</script>
|
|
126
|
-
|
|
127
|
-
<template>
|
|
128
|
-
<div>isDirty: {{ isDirty }}</div>
|
|
129
|
-
<hr />
|
|
130
|
-
<button @click="reset">โป๏ธ Reset</button>
|
|
131
|
-
|
|
132
|
-
<form @submit.prevent="saveChanges">
|
|
133
|
-
<input
|
|
134
|
-
v-model="data.title"
|
|
135
|
-
type="text"
|
|
136
|
-
/>
|
|
137
|
-
<input
|
|
138
|
-
v-model.number="data.year"
|
|
139
|
-
type="number"
|
|
140
|
-
/>
|
|
141
|
-
<input
|
|
142
|
-
v-model="data.isPublished"
|
|
143
|
-
type="checkbox"
|
|
144
|
-
/>
|
|
145
|
-
|
|
146
|
-
<input
|
|
147
|
-
v-model="data.details.director.name"
|
|
148
|
-
type="text"
|
|
149
|
-
/>
|
|
150
|
-
|
|
151
|
-
<button
|
|
152
|
-
type="submit"
|
|
153
|
-
:disabled="!isDirty"
|
|
154
|
-
>
|
|
155
|
-
๐พ Save changes
|
|
156
|
-
</button>
|
|
157
|
-
</form>
|
|
158
|
-
|
|
159
|
-
<h2>Changed data:</h2>
|
|
160
|
-
<pre>{{ changedData }}</pre>
|
|
161
|
-
</template>
|
|
96
|
+
---
|
|
162
97
|
|
|
163
|
-
|
|
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.
|
|
164
101
|
|
|
165
|
-
|
|
102
|
+
```js
|
|
103
|
+
import {useCollection} from 'tracked-instance'
|
|
166
104
|
|
|
167
|
-
|
|
168
|
-
const {isDirty, add, items, remove, reset, loadData} = useCollection()
|
|
105
|
+
const {items, isDirty, add, remove, loadData, reset} = useCollection()
|
|
169
106
|
|
|
170
107
|
loadData([{name: 'Jack'}, {name: 'John'}, {name: 'Joe'}])
|
|
171
108
|
```
|
|
172
|
-
|
|
173
|
-
|
|
109
|
+
|
|
110
|
+
Each item in `items` is a `CollectionItem` with its own `TrackedInstance`:
|
|
111
|
+
|
|
112
|
+
```js
|
|
174
113
|
items.value[0].instance.data.value.name = 'Stepan'
|
|
175
|
-
|
|
114
|
+
isDirty.value // true
|
|
176
115
|
```
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
185
125
|
```
|
|
186
126
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
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
|
|
191
132
|
```
|
|
192
133
|
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
195
139
|
reset()
|
|
196
140
|
```
|
|
197
141
|
|
|
198
|
-
Item meta
|
|
199
|
-
|
|
200
|
-
|
|
142
|
+
### Item meta
|
|
143
|
+
|
|
144
|
+
Attach computed or reactive metadata to each item via a factory function:
|
|
145
|
+
|
|
146
|
+
```js
|
|
201
147
|
const {add, items} = useCollection(instance => ({
|
|
202
148
|
isValidName: computed(() => instance.data.value.name.length > 0)
|
|
203
149
|
}))
|
|
204
150
|
|
|
205
151
|
add({name: ''})
|
|
206
|
-
|
|
207
|
-
console.log(items.value[0].meta.isValidName.value) // false
|
|
152
|
+
items.value[0].meta.isValidName.value // false
|
|
208
153
|
```
|
|
209
154
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const {isDirty, add, items, remove, reset, loadData} = useCollection()
|
|
218
|
-
|
|
219
|
-
loadData([{name: 'Jack'}, {name: 'John'}, {name: 'Joe'}])
|
|
220
|
-
|
|
221
|
-
const newUserName = ref('')
|
|
222
|
-
|
|
223
|
-
const saveChanges = () => {
|
|
224
|
-
loadData(
|
|
225
|
-
items.value
|
|
226
|
-
.filter(item => !item.isRemoved.value)
|
|
227
|
-
.map((item) => item.instance.data.value)
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
</script>
|
|
231
|
-
|
|
232
|
-
<template>
|
|
233
|
-
<button
|
|
234
|
-
:disabled="!isDirty"
|
|
235
|
-
@click="saveChanges"
|
|
236
|
-
>
|
|
237
|
-
๐พ Save changes
|
|
238
|
-
</button>
|
|
239
|
-
<button @click="reset">โป๏ธ Reset</button>
|
|
240
|
-
<hr />
|
|
241
|
-
|
|
242
|
-
<div>isDirty: {{ isDirty }}</div>
|
|
243
|
-
|
|
244
|
-
<div>
|
|
245
|
-
Add new user:
|
|
246
|
-
<input
|
|
247
|
-
v-model="newUserName"
|
|
248
|
-
type="text"
|
|
249
|
-
/>
|
|
250
|
-
<button @click="add({name: newUserName}); newUserName = ''">โ Add user</button>
|
|
251
|
-
</div>
|
|
252
|
-
|
|
253
|
-
<ul>
|
|
254
|
-
<template v-for="(item, index) in items">
|
|
255
|
-
<li v-if="!item.isRemoved.value">
|
|
256
|
-
<input
|
|
257
|
-
v-model="item.instance.data.value.name"
|
|
258
|
-
type="text"
|
|
259
|
-
/>
|
|
260
|
-
<button @click="remove(index)">๐ Remove</button>
|
|
261
|
-
<button
|
|
262
|
-
v-if="!item.isNew.value"
|
|
263
|
-
@click="item.instance.reset()"
|
|
264
|
-
>
|
|
265
|
-
โป๏ธ Reset
|
|
266
|
-
</button>
|
|
267
|
-
isNew: {{ item.isNew.value }}
|
|
268
|
-
</li>
|
|
269
|
-
</template>
|
|
270
|
-
</ul>
|
|
271
|
-
|
|
272
|
-
Removed items:
|
|
273
|
-
<ul>
|
|
274
|
-
<li v-for="item in items.filter((i) => i.isRemoved.value)">
|
|
275
|
-
{{ item.instance.data.value.name }}
|
|
276
|
-
<button @click="item.isRemoved.value = false">โป๏ธ Rollback</button>
|
|
277
|
-
</li>
|
|
278
|
-
</ul>
|
|
279
|
-
</template>
|
|
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
|
+
)
|
|
280
162
|
```
|
|
281
163
|
|
|
282
|
-
|
|
283
|
-
## useTrackedInstance(initialData?, options?)
|
|
164
|
+
---
|
|
284
165
|
|
|
285
|
-
|
|
286
|
-
useTrackedInstance<Data>(initialData?: Data, options?: TrackedInstanceOptions): TrackedInstance<Data>
|
|
287
|
-
```
|
|
166
|
+
## API Reference
|
|
288
167
|
|
|
289
|
-
###
|
|
168
|
+
### useTrackedInstance(initialData?, options?)
|
|
290
169
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
170
|
+
```typescript
|
|
171
|
+
useTrackedInstance<Data>(initialData ? : Data, options ? : TrackedInstanceOptions)
|
|
172
|
+
:
|
|
173
|
+
TrackedInstance<Data>
|
|
174
|
+
```
|
|
294
175
|
|
|
295
|
-
|
|
176
|
+
| Option | Type | Description |
|
|
177
|
+
|----------|---------------------------------------|------------------------------------------------------------|
|
|
178
|
+
| `equals` | `(a: unknown, b: unknown) => boolean` | Custom equality for primitive leaf values. Replaces `===`. |
|
|
296
179
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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. |
|
|
302
187
|
|
|
303
|
-
|
|
188
|
+
### useCollection(createItemMeta?, options?)
|
|
304
189
|
|
|
305
190
|
```typescript
|
|
306
191
|
useCollection<Item, Meta>(
|
|
307
|
-
createItemMeta
|
|
308
|
-
options
|
|
309
|
-
): Collection<Item, Meta>
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
The `options` object (including `equals`) is passed to every `TrackedInstance` created inside the collection โ both from `loadData()` and `add()`.
|
|
313
|
-
|
|
314
|
-
```javascript
|
|
315
|
-
const { items, isDirty } = useCollection(
|
|
316
|
-
() => undefined,
|
|
317
|
-
{ equals: (a, b) => (a ?? '') === (b ?? '') }
|
|
192
|
+
createItemMeta ? : (instance: TrackedInstance<Item>) => Meta,
|
|
193
|
+
options ? : TrackedInstanceOptions,
|
|
318
194
|
)
|
|
195
|
+
:
|
|
196
|
+
Collection<Item, Meta>
|
|
319
197
|
```
|
|
320
198
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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. |
|
|
329
207
|
|
|
330
208
|
### CollectionItem
|
|
331
209
|
|
|
332
210
|
```typescript
|
|
333
211
|
interface CollectionItem<Item, Meta = undefined> {
|
|
334
|
-
instance: TrackedInstance<Item>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
meta: Meta
|
|
338
|
-
remove
|
|
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
|
|
339
217
|
}
|
|
340
218
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tracked-instance",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Build large forms and track all changes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
37
|
-
"url": "git+https://github.com/rudnik275/tracked-instance"
|
|
37
|
+
"url": "git+https://github.com/rudnik275/tracked-instance.git"
|
|
38
38
|
},
|
|
39
39
|
"author": "Dmytro Rudnyk",
|
|
40
40
|
"license": "MIT",
|