@wishbone-media/spark 0.32.0 → 0.34.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishbone-media/spark",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -40,6 +40,7 @@
40
40
  "devDependencies": {
41
41
  "@vitejs/plugin-vue": "^6.0.2",
42
42
  "vite": "^7.2.2",
43
+ "vite-plugin-dts": "^4.5.4",
43
44
  "vue": "^3.5.24"
44
45
  },
45
46
  "packageManager": "pnpm@10.11.1",
@@ -49,6 +50,7 @@
49
50
  "release:minor": "npm version minor && npm publish && git push --follow-tags",
50
51
  "release:major": "npm version major && npm publish && git push --follow-tags"
51
52
  },
53
+ "types": "./dist/index.d.ts",
52
54
  "exports": {
53
55
  ".": "./dist/index.js",
54
56
  "./src/*": "./src/*",
@@ -4,6 +4,7 @@ export { sparkOverlayService } from './sparkOverlayService.js'
4
4
  export { useSparkOverlay } from './useSparkOverlay.js'
5
5
  export { useSparkTableRouteSync } from './useSparkTableRouteSync.js'
6
6
  export { useFormSubmission } from './useFormSubmission.js'
7
+ export { useCrudResource, useTableDelete } from './useCrudResource.js'
7
8
  export { useSubNavigation } from './useSubNavigation.js'
8
9
  export {
9
10
  useFormDirtyGuard,
@@ -0,0 +1,109 @@
1
+ import { ref, computed, onMounted } from 'vue'
2
+ import { useRoute } from 'vue-router'
3
+ import { getAxiosInstance } from '../plugins/axios.js'
4
+ import { sparkNotificationService } from './sparkNotificationService.js'
5
+
6
+ /**
7
+ * Composable for CRUD Save views — handles resource fetching, error state, and edit mode detection.
8
+ *
9
+ * @param {string} endpoint - API endpoint name (e.g. 'tributes', 'public-holidays')
10
+ * @param {Object} options
11
+ * @param {string} options.queryParams - Optional query string for eager loading (e.g. 'with=brand.items')
12
+ * @param {Function} options.fetchFn - Custom fetch function. Receives { axios, resourceId, isEditMode, route }.
13
+ * Return value is assigned to data ref. Return undefined to skip assignment.
14
+ * @param {boolean} options.fetchOnCreate - Whether to call fetch() on mount in create mode (default: false)
15
+ * @returns {{ data, error, resourceId, isEditMode, fetch, handleSuccess }}
16
+ */
17
+ export function useCrudResource(endpoint, options = {}) {
18
+ const { queryParams = '', fetchFn = null, fetchOnCreate = false } = options
19
+ const route = useRoute()
20
+
21
+ const data = ref(null)
22
+ const error = ref(null)
23
+
24
+ const resourceId = computed(() => route.params.id ?? null)
25
+ const isEditMode = computed(() => resourceId.value !== null)
26
+
27
+ async function fetch() {
28
+ if (!fetchOnCreate && !isEditMode.value) return
29
+ error.value = null
30
+ try {
31
+ if (fetchFn) {
32
+ const result = await fetchFn({
33
+ axios: getAxiosInstance(),
34
+ resourceId: resourceId.value,
35
+ isEditMode: isEditMode.value,
36
+ route,
37
+ })
38
+ if (result !== undefined) data.value = result
39
+ } else {
40
+ const axios = getAxiosInstance()
41
+ let url = `/${endpoint}/${resourceId.value}`
42
+ if (queryParams) url += `?${queryParams}`
43
+ const response = await axios.get(url)
44
+ data.value = response.data
45
+ }
46
+ } catch (err) {
47
+ error.value = err.response?.data?.message || err.message || 'Failed to load data.'
48
+ }
49
+ }
50
+
51
+ function handleSuccess() {
52
+ if (isEditMode.value) fetch()
53
+ }
54
+
55
+ onMounted(() => {
56
+ if (fetchOnCreate || isEditMode.value) fetch()
57
+ })
58
+
59
+ return { data, error, resourceId, isEditMode, fetch, handleSuccess }
60
+ }
61
+
62
+ /**
63
+ * Composable for SparkTable row deletion — handles axios delete, notifications, and table refresh.
64
+ *
65
+ * @param {Ref} tableRef - Template ref to the SparkTable component (used for .refresh())
66
+ * @param {Object} options
67
+ * @param {string} options.endpoint - API endpoint name (e.g. 'public-holidays', 'invoices')
68
+ * @param {string} options.successMessage - Notification on success (default: 'Deleted successfully.')
69
+ * @param {string} options.errorMessage - Notification on error (default: API message or 'Failed to delete.')
70
+ * @param {Function} options.onSuccess - Callback after successful delete (receives row)
71
+ * @param {Function} options.onError - Callback on error (receives error, row). Return true to skip default handling.
72
+ * @returns {{ handleDelete }}
73
+ */
74
+ export function useTableDelete(tableRef, options = {}) {
75
+ const {
76
+ endpoint,
77
+ successMessage,
78
+ errorMessage,
79
+ onSuccess,
80
+ onError,
81
+ } = options
82
+
83
+ async function handleDelete(row) {
84
+ try {
85
+ const axios = getAxiosInstance()
86
+ await axios.delete(`/${endpoint}/${row.id}`)
87
+ } catch (err) {
88
+ if (onError) {
89
+ const handled = onError(err, row)
90
+ if (handled) return
91
+ }
92
+ sparkNotificationService.show({
93
+ type: 'danger',
94
+ message: errorMessage || err.response?.data?.message || 'Failed to delete.',
95
+ })
96
+ console.error(err)
97
+ return
98
+ }
99
+
100
+ sparkNotificationService.show({
101
+ type: 'success',
102
+ message: successMessage || 'Deleted successfully.',
103
+ })
104
+ if (onSuccess) onSuccess(row)
105
+ if (tableRef?.value?.refresh) tableRef.value.refresh()
106
+ }
107
+
108
+ return { handleDelete }
109
+ }
@@ -18,12 +18,13 @@ import { parseLaravelErrors, getFormLevelMessage, isValidationError } from '../u
18
18
  * @param {Function} options.onError - Callback after error (receives error, can return true to skip default handling)
19
19
  * @param {boolean} options.showNotification - Whether to show success notification (default: true)
20
20
  * @param {boolean} options.setFieldErrors - Whether to set field-level errors on 422 (default: true)
21
- * @param {boolean} options.setFormErrors - Whether to show form-level error notification on 422 (default: false)
21
+ * @param {boolean} options.setFormErrors - Whether to show form-level error notification on 422 (default: true)
22
22
  *
23
23
  * @returns {Object} Form submission helpers
24
24
  */
25
25
  export function useFormSubmission(options = {}) {
26
26
  const {
27
+ endpoint = null,
27
28
  successMessage = 'Saved successfully!',
28
29
  createMessage = 'Created successfully!',
29
30
  updateMessage = 'Updated successfully!',
@@ -32,7 +33,7 @@ export function useFormSubmission(options = {}) {
32
33
  onError = null,
33
34
  showNotification = true,
34
35
  setFieldErrors = true,
35
- setFormErrors = false,
36
+ setFormErrors = true,
36
37
  } = options
37
38
 
38
39
  const router = useRouter()
@@ -63,20 +64,6 @@ export function useFormSubmission(options = {}) {
63
64
  const response = await submitFn()
64
65
  const data = response.data
65
66
 
66
- // Show success notification
67
- if (showNotification) {
68
- const message = isEditMode
69
- ? updateMessage
70
- : method === 'create'
71
- ? createMessage
72
- : successMessage
73
-
74
- sparkNotificationService.show({
75
- type: 'success',
76
- message,
77
- })
78
- }
79
-
80
67
  // Execute success callback
81
68
  if (onSuccess) {
82
69
  await onSuccess(data)
@@ -90,6 +77,21 @@ export function useFormSubmission(options = {}) {
90
77
  await router.push(location)
91
78
  }
92
79
 
80
+ // Show success notification (after redirect so it appears on the destination page
81
+ // and isn't cleared by SparkNotificationOutlet's route-change detection)
82
+ if (showNotification) {
83
+ const message = isEditMode
84
+ ? updateMessage
85
+ : method === 'create'
86
+ ? createMessage
87
+ : successMessage
88
+
89
+ sparkNotificationService.toast({
90
+ type: 'success',
91
+ message,
92
+ })
93
+ }
94
+
93
95
  return { success: true, data, error: null }
94
96
  } catch (error) {
95
97
  const response = error.response
@@ -115,6 +117,7 @@ export function useFormSubmission(options = {}) {
115
117
  type: 'danger',
116
118
  message: getFormLevelMessage(response),
117
119
  })
120
+ window.scrollTo({ top: 0, behavior: 'smooth' })
118
121
  }
119
122
  } else {
120
123
  // Handle non-validation errors
@@ -122,6 +125,7 @@ export function useFormSubmission(options = {}) {
122
125
  type: 'danger',
123
126
  message: getFormLevelMessage(response),
124
127
  })
128
+ window.scrollTo({ top: 0, behavior: 'smooth' })
125
129
  }
126
130
 
127
131
  return { success: false, data: null, error }
@@ -152,9 +156,28 @@ export function useFormSubmission(options = {}) {
152
156
  })
153
157
  }
154
158
 
159
+ /**
160
+ * Convenience method for standard CRUD form submissions.
161
+ * Automatically constructs URL and HTTP method from endpoint + resourceId.
162
+ * Requires `endpoint` option to be set.
163
+ *
164
+ * @param {Object} config - Submission configuration
165
+ * @param {Object} config.payload - Request payload
166
+ * @param {Object} config.node - FormKit node
167
+ * @param {string|number} config.resourceId - Resource ID (for edit mode)
168
+ * @param {boolean} config.isEditMode - Whether this is an update
169
+ */
170
+ async function submitCrud(config) {
171
+ const { payload, node, resourceId, isEditMode } = config
172
+ const url = isEditMode ? `/${endpoint}/${resourceId}` : `/${endpoint}`
173
+ const method = isEditMode ? 'put' : 'post'
174
+ return submitToApi({ url, payload, method, node, isEditMode })
175
+ }
176
+
155
177
  return {
156
178
  submitting,
157
179
  submit,
158
180
  submitToApi,
181
+ ...(endpoint ? { submitCrud } : {}),
159
182
  }
160
183
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Australian state/territory options for use in select dropdowns and filter controls.
3
+ * Each entry has { label, value } shape compatible with FormKit selects and SparkTable filters.
4
+ */
5
+ export const AUSTRALIAN_STATES = [
6
+ { label: 'Australian Capital Territory', value: 'ACT' },
7
+ { label: 'New South Wales', value: 'NSW' },
8
+ { label: 'Northern Territory', value: 'NT' },
9
+ { label: 'Queensland', value: 'QLD' },
10
+ { label: 'South Australia', value: 'SA' },
11
+ { label: 'Tasmania', value: 'TAS' },
12
+ { label: 'Victoria', value: 'VIC' },
13
+ { label: 'Western Australia', value: 'WA' },
14
+ ]
@@ -0,0 +1 @@
1
+ export { AUSTRALIAN_STATES } from './australianStates.js'
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './components/index.js'
2
2
  export * from './composables/index.js'
3
+ export * from './constants/index.js'
3
4
  export * from './containers/index.js'
4
5
  export * from './plugins/index.js'
5
6
  export * from './stores/index.js'