feat(record-form): dirty tracking and an unsaved-changes guard #90

Merged
pienter merged 3 commits from issue/51-unsaved-changes-guard into main 2026-09-16 01:18:58 +00:00
Owner

RecordForm now owns dirty tracking. A new baseline prop is the last saved state (the first modelValue seen at setup when omitted); dirty is a structural comparison of modelValue against it (isEqualRecord in utils/cms), always false in readonly mode, emitted as update:dirty for v-model:dirty and exposed on the instance.

useUnsavedChanges(dirty) (@pienter/ui/composables/useUnsavedChanges) turns that flag into a leave guard: beforeunload while dirty, a vue-router onBeforeRouteLeave guard registered inside the composable, and { pending, confirmLeave, resolve } for a consumer-rendered AlertDialog.

The five playground forms (Page, Blog, Settings, Company, Contact) keep a baseline ref refreshed after load and after a successful save, bind :baseline and v-model:dirty, and render one destructive "Discard unsaved changes?" AlertDialog bound to pending / resolve.

Closes #51

RecordForm now owns dirty tracking. A new `baseline` prop is the last saved state (the first `modelValue` seen at setup when omitted); `dirty` is a structural comparison of `modelValue` against it (`isEqualRecord` in `utils/cms`), always `false` in `readonly` mode, emitted as `update:dirty` for `v-model:dirty` and exposed on the instance. `useUnsavedChanges(dirty)` (`@pienter/ui/composables/useUnsavedChanges`) turns that flag into a leave guard: `beforeunload` while dirty, a vue-router `onBeforeRouteLeave` guard registered inside the composable, and `{ pending, confirmLeave, resolve }` for a consumer-rendered `AlertDialog`. The five playground forms (Page, Blog, Settings, Company, Contact) keep a `baseline` ref refreshed after load and after a successful save, bind `:baseline` and `v-model:dirty`, and render one destructive "Discard unsaved changes?" `AlertDialog` bound to `pending` / `resolve`. Closes #51
feat(examples): unsaved-changes guard on the record forms
All checks were successful
ci / lint (pull_request) Successful in 44s
ci / typecheck (pull_request) Successful in 39s
ci / format (pull_request) Successful in 36s
ci / release (pull_request) Has been skipped
af741fd9b7
pienter force-pushed issue/51-unsaved-changes-guard from af741fd9b7
All checks were successful
ci / lint (pull_request) Successful in 44s
ci / typecheck (pull_request) Successful in 39s
ci / format (pull_request) Successful in 36s
ci / release (pull_request) Has been skipped
to 1f7a064e2c
All checks were successful
ci / lint (pull_request) Successful in 44s
ci / typecheck (pull_request) Successful in 21s
ci / format (pull_request) Successful in 20s
ci / release (pull_request) Has been skipped
2026-09-16 01:10:47 +00:00
Compare
merge
All checks were successful
ci / lint (pull_request) Successful in 40s
ci / typecheck (pull_request) Successful in 27s
ci / format (pull_request) Successful in 24s
ci / release (pull_request) Has been skipped
707ed40900
pienter deleted branch issue/51-unsaved-changes-guard 2026-09-16 01:18:58 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pienter/ui!90
No description provided.