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.
Files changed (2) hide show
  1. package/README.md +143 -265
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,340 +1,218 @@
1
- # Tracked instance
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
- # ๐Ÿš€ Features
5
- - ๐Ÿ•ถ Track what changed in your form
6
- - ๐ŸŒŽ Send on backend only fields which changed
7
- - ๐Ÿ“ฆ Build multiple requests only for items that have been changed/removed/added
8
- - ๐Ÿฆพ Type Strong: Written in TypeScript
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
- # Description
11
- Build large forms and send all requests in one take.
12
- Combination of useTrackedInstance and useCollection can manage very large form with entities which deeply related each other.
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
- # Install
17
- > npm i tracked-instance
26
+ Supports **Vue 3** only.
18
27
 
19
- # Support
20
- Supports Vue 3.x only
28
+ ---
21
29
 
22
- # Usage
30
+ ## useTrackedInstance &nbsp;ยท&nbsp; [โ–ถ 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
- ## Tracked instance
32
+ Track changes to a single object, primitive, or array.
25
33
 
26
- Track everything what was changed
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
- Do some changes and see only changed field in changedData.
35
- Then set previous value and see what changedData is empty.
36
- That guaranty what you always get real changes
37
- ```javascript
42
+
43
+ **Mutate `data.value` directly** โ€” `changedData` and `isDirty` update automatically:
44
+
45
+ ```js
38
46
  data.value.name = 'John'
39
- console.log(isDirty.value) // true
40
- console.log(changedData.value) // {name: 'John'}
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
- console.log(isDirty.value) // false
44
- console.log(changedData.value) // undefined
52
+ isDirty.value // false
53
+ changedData.value // undefined
45
54
  ```
46
- Rollback initial value:
47
- ```javascript
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
- console.log(data.value) // { name: 'Jack', isActive: false }
51
- console.log(isDirty.value) // false
52
- console.log(changedData.value) // undefined
61
+ data.value // { name: 'Jack', isActive: false }
53
62
  ```
54
- All changes should be replaced by new loaded data.
55
- The data will be considered not dirty
56
- ```javascript
57
- data.value.name = 'John'
58
- data.value.isActive = true
59
- loadData({
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
- Can accept primitive values or arrays
68
- ```javascript
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` option
78
+ ### Custom equality with `equals`
74
79
 
75
- By default, values are compared with strict equality (`===`). You can override this with a custom `equals` function that is called for primitive leaf values.
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
- A common use case is treating `null` and `""` as equivalent โ€” for example, when a UI component (like Vuetify's text field with a clear button) sets a value to `null`, but the original data used `""`:
78
-
79
- ```javascript
80
- const { data, isDirty } = useTrackedInstance(
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 = '' // treated as equal to null
86
- console.log(isDirty.value) // false
89
+ data.value.comment = '' // treated as equal to null
90
+ isDirty.value // false
87
91
 
88
- data.value.comment = 'hello'
89
- console.log(isDirty.value) // true
92
+ data.value.comment = 'hi'
93
+ isDirty.value // true
90
94
  ```
91
95
 
92
- The `equals` function receives the two values being compared and should return `true` if they are considered equal. It is only called for primitive values โ€” object and array fields are always compared by recursing into their properties.
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 &nbsp;ยท&nbsp; [โ–ถ 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
- ## Collection
102
+ ```js
103
+ import {useCollection} from 'tracked-instance'
166
104
 
167
- ```javascript
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
- Should be dirty on make some changes, remove or add item
173
- ```javascript
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
- console.log(isDirty.value) // true
114
+ isDirty.value // true
176
115
  ```
177
- Add new item:
178
- ```javascript
179
- const addedItem = add({name: 'Taras'})
180
- console.log(addedItem) // {instance: TrackedInstance<{name: 'Taras'}>, isRemoved: false, isNew: true, meta: undefined}
181
- ```
182
- Add new item in specific position:
183
- ```javascript
184
- add({name: 'Taras'}, 0)
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
- Item should be softly removed and can be reverted by reset()
188
- ```javascript
189
- remove(0)
190
- remove(0, true) // hard remove
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
- Reset all changes including changing data on each item
194
- ```javascript
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. Additional custom fields which can watch on item instance.
199
- If set then should be applied to each item which was added by add() or loadData()
200
- ```javascript
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
- ### Real-world example
211
- [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==)
212
- ```vue
213
- <script setup>
214
- import {ref} from 'vue'
215
- import {useCollection} from 'tracked-instance'
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
- # Documentation
283
- ## useTrackedInstance(initialData?, options?)
164
+ ---
284
165
 
285
- ```typescript
286
- useTrackedInstance<Data>(initialData?: Data, options?: TrackedInstanceOptions): TrackedInstance<Data>
287
- ```
166
+ ## API Reference
288
167
 
289
- ### Options
168
+ ### useTrackedInstance(initialData?, options?)
290
169
 
291
- | Option | Type | Description |
292
- |--------|------|-------------|
293
- | `equals` | `(a: unknown, b: unknown) => boolean` | Custom equality function for primitive leaf values. Replaces default `===`. |
170
+ ```typescript
171
+ useTrackedInstance<Data>(initialData ? : Data, options ? : TrackedInstanceOptions)
172
+ :
173
+ TrackedInstance<Data>
174
+ ```
294
175
 
295
- ### TrackedInstance
176
+ | Option | Type | Description |
177
+ |----------|---------------------------------------|------------------------------------------------------------|
178
+ | `equals` | `(a: unknown, b: unknown) => boolean` | Custom equality for primitive leaf values. Replaces `===`. |
296
179
 
297
- - **data** โ€” reactive reference to the current data. Mutate it directly to track changes.
298
- - **changedData** โ€” only the modified fields, deeply nested. `undefined` when nothing has changed.
299
- - **isDirty** โ€” `true` when any field differs from the original.
300
- - **loadData(newData)** โ€” replace data and clear dirty state (new baseline).
301
- - **reset()** โ€” revert all changes back to the last `loadData()` baseline.
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
- ## useCollection(createItemMeta?, options?)
188
+ ### useCollection(createItemMeta?, options?)
304
189
 
305
190
  ```typescript
306
191
  useCollection<Item, Meta>(
307
- createItemMeta?: (instance: TrackedInstance<Item>) => Meta,
308
- options?: TrackedInstanceOptions,
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
- ### Collection
322
-
323
- - **items** โ€” reactive array of `CollectionItem`.
324
- - **isDirty** โ€” `true` if any item is dirty, new, or soft-removed.
325
- - **add(item, index?)** โ€” add a new item (marked `isNew`). Appended to end by default.
326
- - **remove(index, isHardRemove?)** โ€” soft-remove by default (`isRemoved = true`). Pass `true` to hard-delete from array.
327
- - **loadData(items)** โ€” replace all items and clear dirty state.
328
- - **reset()** โ€” remove new items, restore soft-removed items, and reset all instance data.
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> // the tracked instance for this item
335
- isRemoved: Ref<boolean> // true after soft remove
336
- isNew: Ref<boolean> // true for items added via add(), false for items loaded via loadData()
337
- meta: Meta // custom metadata from createItemMeta(), undefined by default
338
- remove: (isHardRemove?: boolean) => void // shortcut: removes self from collection
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.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",