@wishbone-media/spark 0.19.0 → 0.20.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/dist/index.js +1120 -1029
- package/package.json +1 -1
- package/src/assets/css/index.css +1 -0
- package/src/assets/css/nprogress.css +6 -0
- package/src/composables/sparkModalService.js +49 -0
- package/src/composables/useSparkTableRouteSync.js +19 -2
- package/src/utils/sparkTable/renderers/actions.js +27 -2
- package/src/utils/sparkTable/renderers/boolean.js +97 -0
- package/src/utils/sparkTable/renderers/index.js +3 -0
package/package.json
CHANGED
package/src/assets/css/index.css
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { reactive, markRaw } from 'vue'
|
|
2
|
+
import SparkModalDialog from '@/components/SparkModalDialog.vue'
|
|
2
3
|
|
|
3
4
|
class SparkModalService {
|
|
4
5
|
constructor() {
|
|
@@ -21,6 +22,54 @@ class SparkModalService {
|
|
|
21
22
|
this.state.isVisible = false
|
|
22
23
|
this.state.eventHandlers = {}
|
|
23
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Show a confirmation dialog and return a Promise
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} options - Confirmation options
|
|
30
|
+
* @param {string} [options.title='Confirm'] - Dialog title
|
|
31
|
+
* @param {string} [options.message='Are you sure?'] - Dialog message
|
|
32
|
+
* @param {string} [options.type='warning'] - Dialog type (info, success, warning, danger)
|
|
33
|
+
* @param {string} [options.confirmText='Confirm'] - Confirm button text
|
|
34
|
+
* @param {string} [options.cancelText='Cancel'] - Cancel button text
|
|
35
|
+
* @param {string} [options.confirmVariant='primary'] - Confirm button variant
|
|
36
|
+
* @returns {Promise<boolean>} - Resolves to true if confirmed, false if cancelled
|
|
37
|
+
*/
|
|
38
|
+
confirm = (options = {}) => {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const {
|
|
41
|
+
title = 'Confirm',
|
|
42
|
+
message = 'Are you sure?',
|
|
43
|
+
type = 'warning',
|
|
44
|
+
confirmText = 'Confirm',
|
|
45
|
+
cancelText = 'Cancel',
|
|
46
|
+
confirmVariant = 'primary',
|
|
47
|
+
} = options
|
|
48
|
+
|
|
49
|
+
this.show(
|
|
50
|
+
SparkModalDialog,
|
|
51
|
+
{
|
|
52
|
+
title,
|
|
53
|
+
message,
|
|
54
|
+
type,
|
|
55
|
+
buttons: [
|
|
56
|
+
{ text: confirmText, variant: confirmVariant, event: 'confirm' },
|
|
57
|
+
{ text: cancelText, variant: 'secondary', event: 'cancel' },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
confirm: () => {
|
|
62
|
+
this.hide()
|
|
63
|
+
resolve(true)
|
|
64
|
+
},
|
|
65
|
+
cancel: () => {
|
|
66
|
+
this.hide()
|
|
67
|
+
resolve(false)
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
}
|
|
24
73
|
}
|
|
25
74
|
|
|
26
75
|
export const sparkModalService = new SparkModalService()
|
|
@@ -203,6 +203,19 @@ export const useSparkTableRouteSync = (sparkTable, options = {}) => {
|
|
|
203
203
|
return false
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Coerce URL query param values to their proper types
|
|
208
|
+
* URL params are always strings, but page/limit should be integers
|
|
209
|
+
*/
|
|
210
|
+
const coerceParamValue = (key, value) => {
|
|
211
|
+
const numericParams = ['page', 'limit']
|
|
212
|
+
if (numericParams.includes(key) && value !== null && value !== undefined) {
|
|
213
|
+
const parsed = parseInt(value, 10)
|
|
214
|
+
return isNaN(parsed) ? value : parsed
|
|
215
|
+
}
|
|
216
|
+
return value
|
|
217
|
+
}
|
|
218
|
+
|
|
206
219
|
/**
|
|
207
220
|
* Restore params from route query to sparkTable
|
|
208
221
|
*/
|
|
@@ -211,7 +224,7 @@ export const useSparkTableRouteSync = (sparkTable, options = {}) => {
|
|
|
211
224
|
// Flat mode: read any query param that looks like a table param
|
|
212
225
|
Object.keys(route.query).forEach((key) => {
|
|
213
226
|
if (isTableParam(key)) {
|
|
214
|
-
sparkTable.params[key] = route.query[key]
|
|
227
|
+
sparkTable.params[key] = coerceParamValue(key, route.query[key])
|
|
215
228
|
}
|
|
216
229
|
})
|
|
217
230
|
} else {
|
|
@@ -224,7 +237,7 @@ export const useSparkTableRouteSync = (sparkTable, options = {}) => {
|
|
|
224
237
|
// e.g., "table[selectFilters[is_quote]]" => "selectFilters[is_quote]"
|
|
225
238
|
// e.g., "table[page]" => "page"
|
|
226
239
|
const paramKey = key.slice(prefix.length, -1)
|
|
227
|
-
sparkTable.params[paramKey] = route.query[key]
|
|
240
|
+
sparkTable.params[paramKey] = coerceParamValue(paramKey, route.query[key])
|
|
228
241
|
}
|
|
229
242
|
})
|
|
230
243
|
}
|
|
@@ -275,6 +288,10 @@ export const useSparkTableRouteSync = (sparkTable, options = {}) => {
|
|
|
275
288
|
if (!hasUrlParams && persistToStorage) {
|
|
276
289
|
const storedParams = loadFromStorage()
|
|
277
290
|
if (storedParams && Object.keys(storedParams).length > 0) {
|
|
291
|
+
// Coerce numeric params in case localStorage has string values from previous buggy saves
|
|
292
|
+
Object.keys(storedParams).forEach((key) => {
|
|
293
|
+
storedParams[key] = coerceParamValue(key, storedParams[key])
|
|
294
|
+
})
|
|
278
295
|
Object.assign(sparkTable.params, storedParams)
|
|
279
296
|
}
|
|
280
297
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { icon } from '@fortawesome/fontawesome-svg-core'
|
|
2
|
+
import { sparkModalService } from '@/composables/sparkModalService'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Spark Actions Renderer
|
|
@@ -25,6 +26,16 @@ import { icon } from '@fortawesome/fontawesome-svg-core'
|
|
|
25
26
|
* {
|
|
26
27
|
* icon: 'download',
|
|
27
28
|
* handler: (row) => downloadFile(row.file_url)
|
|
29
|
+
* },
|
|
30
|
+
* {
|
|
31
|
+
* icon: 'trash',
|
|
32
|
+
* label: 'Delete',
|
|
33
|
+
* event: 'deleteRow',
|
|
34
|
+
* confirm: true,
|
|
35
|
+
* confirmTitle: 'Delete Item',
|
|
36
|
+
* confirmType: 'danger',
|
|
37
|
+
* confirmText: 'Delete',
|
|
38
|
+
* cancelText: 'Keep'
|
|
28
39
|
* }
|
|
29
40
|
* ]
|
|
30
41
|
* }
|
|
@@ -37,6 +48,11 @@ import { icon } from '@fortawesome/fontawesome-svg-core'
|
|
|
37
48
|
* @property {string} [event] - Event name to emit (e.g., 'editRow')
|
|
38
49
|
* @property {Function} [handler] - Custom handler function (row) => void
|
|
39
50
|
* @property {boolean|string} [confirm] - Show confirmation dialog (true or custom message)
|
|
51
|
+
* @property {string} [confirmTitle] - Custom confirmation dialog title (default: 'Confirm')
|
|
52
|
+
* @property {string} [confirmType] - Confirmation dialog type: 'info', 'success', 'warning', 'danger' (default: 'warning')
|
|
53
|
+
* @property {string} [confirmText] - Confirm button text (default: 'Confirm')
|
|
54
|
+
* @property {string} [cancelText] - Cancel button text (default: 'Cancel')
|
|
55
|
+
* @property {string} [confirmVariant] - Confirm button variant (default: 'primary')
|
|
40
56
|
* @property {Function} [condition] - Conditional visibility (row) => boolean
|
|
41
57
|
*/
|
|
42
58
|
|
|
@@ -89,7 +105,7 @@ export const actionsRenderer = (sparkTable) => {
|
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
// Add click handler
|
|
92
|
-
button.addEventListener('click', (e) => {
|
|
108
|
+
button.addEventListener('click', async (e) => {
|
|
93
109
|
e.preventDefault()
|
|
94
110
|
e.stopPropagation()
|
|
95
111
|
|
|
@@ -100,7 +116,16 @@ export const actionsRenderer = (sparkTable) => {
|
|
|
100
116
|
? action.confirm
|
|
101
117
|
: `Are you sure you want to ${action.label?.toLowerCase() || 'perform this action'}?`
|
|
102
118
|
|
|
103
|
-
|
|
119
|
+
const confirmed = await sparkModalService.confirm({
|
|
120
|
+
title: action.confirmTitle,
|
|
121
|
+
message: confirmMessage,
|
|
122
|
+
type: action.confirmType,
|
|
123
|
+
confirmText: action.confirmText,
|
|
124
|
+
cancelText: action.cancelText,
|
|
125
|
+
confirmVariant: action.confirmVariant,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if (!confirmed) {
|
|
104
129
|
return
|
|
105
130
|
}
|
|
106
131
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { icon } from '@fortawesome/fontawesome-svg-core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Spark Boolean Renderer
|
|
5
|
+
*
|
|
6
|
+
* Renders boolean-like values as check/times icons in a circular container
|
|
7
|
+
*
|
|
8
|
+
* Truthy values: true, 1, '1', 'yes', 'true'
|
|
9
|
+
* Falsy values: false, 0, '0', 'no', 'false', null, undefined, ''
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* {
|
|
13
|
+
* data: 'is_active',
|
|
14
|
+
* renderer: 'spark.boolean',
|
|
15
|
+
* rendererConfig: {
|
|
16
|
+
* trueIcon: 'check', // Default: 'check'
|
|
17
|
+
* falseIcon: 'xmark', // Default: 'xmark'
|
|
18
|
+
* trueColor: 'green', // Default: 'green'
|
|
19
|
+
* falseColor: 'red', // Default: 'red'
|
|
20
|
+
* size: 32, // Default: 32 (px)
|
|
21
|
+
* iconPrefix: 'far' // Default: 'far' (solid)
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const colorClasses = {
|
|
27
|
+
green: { bg: 'bg-green-100', text: 'text-green-500' },
|
|
28
|
+
red: { bg: 'bg-red-100', text: 'text-red-500' },
|
|
29
|
+
yellow: { bg: 'bg-yellow-100', text: 'text-yellow-500' },
|
|
30
|
+
blue: { bg: 'bg-blue-100', text: 'text-blue-500' },
|
|
31
|
+
gray: { bg: 'bg-gray-100', text: 'text-gray-500' },
|
|
32
|
+
purple: { bg: 'bg-purple-100', text: 'text-purple-500' },
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Determines if a value is truthy in the context of boolean display
|
|
37
|
+
* @param {*} value - The value to check
|
|
38
|
+
* @returns {boolean}
|
|
39
|
+
*/
|
|
40
|
+
const isTruthy = (value) => {
|
|
41
|
+
if (value === null || value === undefined || value === '') {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === 'boolean') {
|
|
45
|
+
return value
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === 'number') {
|
|
48
|
+
return value === 1
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === 'string') {
|
|
51
|
+
const normalized = value.toLowerCase().trim()
|
|
52
|
+
return normalized === '1' || normalized === 'yes' || normalized === 'true'
|
|
53
|
+
}
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const booleanRenderer = (sparkTable) => {
|
|
58
|
+
return (instance, td, row, col, prop, value, cellProperties) => {
|
|
59
|
+
// Clear cell
|
|
60
|
+
td.innerHTML = ''
|
|
61
|
+
td.classList.add('spark-table-cell-boolean')
|
|
62
|
+
|
|
63
|
+
const config = cellProperties.rendererConfig || {}
|
|
64
|
+
|
|
65
|
+
const truthy = isTruthy(value)
|
|
66
|
+
const iconName = truthy ? (config.trueIcon || 'check') : (config.falseIcon || 'xmark')
|
|
67
|
+
const colorName = truthy ? (config.trueColor || 'green') : (config.falseColor || 'red')
|
|
68
|
+
const size = config.size || 32
|
|
69
|
+
const iconPrefix = config.iconPrefix || 'far'
|
|
70
|
+
|
|
71
|
+
const colors = colorClasses[colorName] || colorClasses.gray
|
|
72
|
+
|
|
73
|
+
// Create circular container
|
|
74
|
+
const container = document.createElement('div')
|
|
75
|
+
container.classList.add(
|
|
76
|
+
'inline-flex',
|
|
77
|
+
'items-center',
|
|
78
|
+
'justify-center',
|
|
79
|
+
'rounded-full',
|
|
80
|
+
colors.bg,
|
|
81
|
+
colors.text,
|
|
82
|
+
)
|
|
83
|
+
container.style.width = `${size}px`
|
|
84
|
+
container.style.height = `${size}px`
|
|
85
|
+
|
|
86
|
+
// Create icon
|
|
87
|
+
const iconElement = document.createElement('span')
|
|
88
|
+
iconElement.innerHTML = icon({ prefix: iconPrefix, iconName }).html
|
|
89
|
+
iconElement.classList.add('flex', 'items-center', 'justify-center')
|
|
90
|
+
// Icon size is roughly 50% of container
|
|
91
|
+
const iconSize = Math.round(size * 0.5)
|
|
92
|
+
iconElement.style.fontSize = `${iconSize}px`
|
|
93
|
+
|
|
94
|
+
container.appendChild(iconElement)
|
|
95
|
+
td.appendChild(container)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { baseRenderer, registerRenderer } from 'handsontable/renderers'
|
|
2
2
|
import { actionsRenderer } from './actions.js'
|
|
3
3
|
import { badgeRenderer } from './badge.js'
|
|
4
|
+
import { booleanRenderer } from './boolean.js'
|
|
4
5
|
import { linkRenderer } from './link.js'
|
|
5
6
|
import { imageRenderer } from './image.js'
|
|
6
7
|
import { dateRenderer } from './date.js'
|
|
@@ -50,6 +51,7 @@ export const getRenderer = (key) => {
|
|
|
50
51
|
* Registers the following renderers:
|
|
51
52
|
* - spark.actions: Inline action buttons
|
|
52
53
|
* - spark.badge: Colored status badges
|
|
54
|
+
* - spark.boolean: Boolean indicators with check/times icons
|
|
53
55
|
* - spark.link: Clickable links (email, phone, custom)
|
|
54
56
|
* - spark.image: Image thumbnails
|
|
55
57
|
* - spark.date: Formatted dates
|
|
@@ -61,6 +63,7 @@ export const registerSparkRenderers = (sparkTable) => {
|
|
|
61
63
|
// Register Spark renderers
|
|
62
64
|
register('spark.actions', actionsRenderer(sparkTable))
|
|
63
65
|
register('spark.badge', badgeRenderer(sparkTable))
|
|
66
|
+
register('spark.boolean', booleanRenderer(sparkTable))
|
|
64
67
|
register('spark.link', linkRenderer(sparkTable))
|
|
65
68
|
register('spark.image', imageRenderer(sparkTable))
|
|
66
69
|
register('spark.date', dateRenderer(sparkTable))
|