@wishbone-media/spark 0.20.0 → 0.22.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 +2042 -1342
- package/package.json +1 -1
- package/src/assets/css/spark-table.css +13 -0
- package/src/components/SparkAlert.vue +5 -1
- package/src/components/SparkButton.vue +2 -0
- package/src/components/SparkNotificationOutlet.vue +68 -0
- package/src/components/SparkOverlay.vue +15 -2
- package/src/components/SparkTable.vue +23 -0
- package/src/components/SparkToastContainer.vue +184 -0
- package/src/components/index.js +2 -0
- package/src/composables/index.js +1 -0
- package/src/composables/sparkNotificationService.js +459 -0
- package/src/composables/sparkOverlayService.js +4 -4
- package/src/composables/useSparkOverlay.js +6 -3
- package/src/utils/formatTemporal.js +335 -0
- package/src/utils/index.js +1 -0
- package/src/utils/sparkTable/renderers/boolean.js +3 -0
- package/src/utils/sparkTable/renderers/datetime.js +106 -0
- package/src/utils/sparkTable/renderers/index.js +2 -0
package/package.json
CHANGED
|
@@ -9,6 +9,19 @@
|
|
|
9
9
|
--ht-header-highlighted-background-color: transparent;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
.spark-table .handsontable td.current {
|
|
13
|
+
@apply relative;
|
|
14
|
+
|
|
15
|
+
&::before {
|
|
16
|
+
@apply absolute opacity-[0.14] content-[''] inset-0 bg-[var(--ht-cell-selection-background-color,#1a42e8)];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.spark-table .handsontable tr.ht__row_odd:hover td,
|
|
21
|
+
.spark-table .handsontable tr.ht__row_even:hover td {
|
|
22
|
+
@apply !bg-gray-100;
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
.spark-table .ht_master .wtBorder {
|
|
13
26
|
@apply !hidden;
|
|
14
27
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<div class="ml-3">
|
|
8
8
|
<slot />
|
|
9
9
|
</div>
|
|
10
|
-
<div class="ml-auto pl-3 pt-1 self-start">
|
|
10
|
+
<div v-if="closeable" class="ml-auto pl-3 pt-1 self-start">
|
|
11
11
|
<div class="-mx-1.5 -my-1.5">
|
|
12
12
|
<button
|
|
13
13
|
type="button"
|
|
@@ -33,6 +33,10 @@ const props = defineProps({
|
|
|
33
33
|
default: 'info',
|
|
34
34
|
validator: (value) => ['success', 'warning', 'danger', 'info'].includes(value),
|
|
35
35
|
},
|
|
36
|
+
closeable: {
|
|
37
|
+
type: Boolean,
|
|
38
|
+
default: true,
|
|
39
|
+
},
|
|
36
40
|
})
|
|
37
41
|
|
|
38
42
|
defineEmits(['close'])
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Transition
|
|
3
|
+
enter-active-class="transition-all duration-300 ease-out"
|
|
4
|
+
enter-from-class="opacity-0 -translate-y-2"
|
|
5
|
+
enter-to-class="opacity-100 translate-y-0"
|
|
6
|
+
leave-active-class="transition-all duration-200 ease-in"
|
|
7
|
+
leave-from-class="opacity-100 translate-y-0"
|
|
8
|
+
leave-to-class="opacity-0 -translate-y-2"
|
|
9
|
+
mode="out-in"
|
|
10
|
+
>
|
|
11
|
+
<SparkAlert
|
|
12
|
+
v-if="outlet.state.isVisible"
|
|
13
|
+
:key="notificationKey"
|
|
14
|
+
:type="outlet.state.type"
|
|
15
|
+
:closeable="outlet.state.closeable"
|
|
16
|
+
@close="handleClose"
|
|
17
|
+
@mouseenter="handleMouseEnter"
|
|
18
|
+
@mouseleave="handleMouseLeave"
|
|
19
|
+
>
|
|
20
|
+
<component
|
|
21
|
+
v-if="outlet.state.component"
|
|
22
|
+
:is="outlet.state.component"
|
|
23
|
+
v-bind="outlet.state.props"
|
|
24
|
+
/>
|
|
25
|
+
<template v-else>
|
|
26
|
+
{{ outlet.state.message }}
|
|
27
|
+
</template>
|
|
28
|
+
</SparkAlert>
|
|
29
|
+
</Transition>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup>
|
|
33
|
+
import { computed, ref, watch } from 'vue'
|
|
34
|
+
import { sparkNotificationService } from '@/composables/sparkNotificationService'
|
|
35
|
+
import SparkAlert from '@/components/SparkAlert.vue'
|
|
36
|
+
|
|
37
|
+
const props = defineProps({
|
|
38
|
+
name: {
|
|
39
|
+
type: String,
|
|
40
|
+
default: 'default',
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const outlet = computed(() => sparkNotificationService.getOutlet(props.name))
|
|
45
|
+
|
|
46
|
+
// Generate unique key for each notification to trigger transition on content change
|
|
47
|
+
const notificationKey = ref(0)
|
|
48
|
+
watch(
|
|
49
|
+
() => [outlet.value.state.message, outlet.value.state.component, outlet.value.state.type],
|
|
50
|
+
() => {
|
|
51
|
+
if (outlet.value.state.isVisible) {
|
|
52
|
+
notificationKey.value++
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const handleClose = () => {
|
|
58
|
+
sparkNotificationService.hide(props.name)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const handleMouseEnter = () => {
|
|
62
|
+
sparkNotificationService.pause(props.name)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const handleMouseLeave = () => {
|
|
66
|
+
sparkNotificationService.resume(props.name)
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
<DialogPanel
|
|
35
35
|
ref="panelRef"
|
|
36
36
|
:class="[
|
|
37
|
-
'flex
|
|
37
|
+
'flex py-2.5',
|
|
38
|
+
widthClass,
|
|
38
39
|
position === 'left' ? 'relative left-[10px]' : 'absolute right-[10px] h-full',
|
|
39
40
|
]"
|
|
40
41
|
>
|
|
@@ -52,11 +53,19 @@
|
|
|
52
53
|
</template>
|
|
53
54
|
|
|
54
55
|
<script setup>
|
|
55
|
-
import { ref } from 'vue'
|
|
56
|
+
import { computed, ref } from 'vue'
|
|
56
57
|
import { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'
|
|
57
58
|
|
|
58
59
|
const panelRef = ref(null)
|
|
59
60
|
|
|
61
|
+
const sizeClasses = {
|
|
62
|
+
xs: 'w-[250px]',
|
|
63
|
+
sm: 'w-[300px]',
|
|
64
|
+
md: 'w-[450px]',
|
|
65
|
+
lg: 'w-[810px]',
|
|
66
|
+
xl: 'w-[1000px]',
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
const props = defineProps({
|
|
61
70
|
position: {
|
|
62
71
|
type: String,
|
|
@@ -72,6 +81,10 @@ const props = defineProps({
|
|
|
72
81
|
|
|
73
82
|
const emit = defineEmits(['close'])
|
|
74
83
|
|
|
84
|
+
const widthClass = computed(() => {
|
|
85
|
+
return sizeClasses[props.overlayInstance.state.size] || sizeClasses.md
|
|
86
|
+
})
|
|
87
|
+
|
|
75
88
|
const handleClose = () => {
|
|
76
89
|
props.overlayInstance.close()
|
|
77
90
|
emit('close')
|
|
@@ -355,6 +355,29 @@ const sparkTable = reactive({
|
|
|
355
355
|
}
|
|
356
356
|
return stretchedWidth
|
|
357
357
|
},
|
|
358
|
+
/**
|
|
359
|
+
* Copy displayed cell content instead of raw data values
|
|
360
|
+
* This ensures custom renderers copy their visual output, not the underlying data
|
|
361
|
+
*/
|
|
362
|
+
beforeCopy: (data, coords) => {
|
|
363
|
+
const hot = table.value?.hotInstance
|
|
364
|
+
if (!hot) return
|
|
365
|
+
|
|
366
|
+
coords.forEach((range) => {
|
|
367
|
+
for (let row = range.startRow; row <= range.endRow; row++) {
|
|
368
|
+
for (let col = range.startCol; col <= range.endCol; col++) {
|
|
369
|
+
const td = hot.getCell(row, col)
|
|
370
|
+
if (td) {
|
|
371
|
+
const dataRow = row - coords[0].startRow
|
|
372
|
+
const dataCol = col - coords[0].startCol
|
|
373
|
+
// Prefer data-copy-value (for renderers like boolean with icons)
|
|
374
|
+
// Fall back to textContent for standard rendered content
|
|
375
|
+
data[dataRow][dataCol] = td.dataset.copyValue ?? td.textContent ?? ''
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
},
|
|
358
381
|
},
|
|
359
382
|
...props.settings,
|
|
360
383
|
})),
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<!-- Top Left -->
|
|
4
|
+
<div class="fixed top-4 left-4 z-[2000] flex flex-col gap-3 max-w-sm w-full pointer-events-none">
|
|
5
|
+
<TransitionGroup
|
|
6
|
+
enter-active-class="transition-all duration-300 ease-out"
|
|
7
|
+
enter-from-class="opacity-0 -translate-x-4"
|
|
8
|
+
enter-to-class="opacity-100 translate-x-0"
|
|
9
|
+
leave-active-class="transition-all duration-200 ease-in"
|
|
10
|
+
leave-from-class="opacity-100 translate-x-0"
|
|
11
|
+
leave-to-class="opacity-0 -translate-x-4"
|
|
12
|
+
>
|
|
13
|
+
<div v-for="toast in topLeftToasts" :key="toast.id" class="pointer-events-auto">
|
|
14
|
+
<SparkAlert
|
|
15
|
+
:type="toast.type"
|
|
16
|
+
:closeable="toast.closeable"
|
|
17
|
+
@close="handleClose(toast.id)"
|
|
18
|
+
@mouseenter="handleMouseEnter(toast.id)"
|
|
19
|
+
@mouseleave="handleMouseLeave(toast.id)"
|
|
20
|
+
>
|
|
21
|
+
<component
|
|
22
|
+
v-if="toast.component"
|
|
23
|
+
:is="toast.component"
|
|
24
|
+
v-bind="toast.props"
|
|
25
|
+
/>
|
|
26
|
+
<template v-else>{{ toast.message }}</template>
|
|
27
|
+
</SparkAlert>
|
|
28
|
+
</div>
|
|
29
|
+
</TransitionGroup>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- Top Right -->
|
|
33
|
+
<div class="fixed top-4 right-4 z-[2000] flex flex-col gap-3 max-w-sm w-full pointer-events-none">
|
|
34
|
+
<TransitionGroup
|
|
35
|
+
enter-active-class="transition-all duration-300 ease-out"
|
|
36
|
+
enter-from-class="opacity-0 translate-x-4"
|
|
37
|
+
enter-to-class="opacity-100 translate-x-0"
|
|
38
|
+
leave-active-class="transition-all duration-200 ease-in"
|
|
39
|
+
leave-from-class="opacity-100 translate-x-0"
|
|
40
|
+
leave-to-class="opacity-0 translate-x-4"
|
|
41
|
+
>
|
|
42
|
+
<div v-for="toast in topRightToasts" :key="toast.id" class="pointer-events-auto">
|
|
43
|
+
<SparkAlert
|
|
44
|
+
:type="toast.type"
|
|
45
|
+
:closeable="toast.closeable"
|
|
46
|
+
@close="handleClose(toast.id)"
|
|
47
|
+
@mouseenter="handleMouseEnter(toast.id)"
|
|
48
|
+
@mouseleave="handleMouseLeave(toast.id)"
|
|
49
|
+
>
|
|
50
|
+
<component
|
|
51
|
+
v-if="toast.component"
|
|
52
|
+
:is="toast.component"
|
|
53
|
+
v-bind="toast.props"
|
|
54
|
+
/>
|
|
55
|
+
<template v-else>{{ toast.message }}</template>
|
|
56
|
+
</SparkAlert>
|
|
57
|
+
</div>
|
|
58
|
+
</TransitionGroup>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Center (slightly above center) -->
|
|
62
|
+
<div class="fixed top-[40%] left-1/2 -translate-x-1/2 -translate-y-1/2 z-[2000] flex flex-col gap-3 max-w-sm w-full pointer-events-none">
|
|
63
|
+
<TransitionGroup
|
|
64
|
+
enter-active-class="transition-all duration-300 ease-out"
|
|
65
|
+
enter-from-class="opacity-0 scale-95"
|
|
66
|
+
enter-to-class="opacity-100 scale-100"
|
|
67
|
+
leave-active-class="transition-all duration-200 ease-in"
|
|
68
|
+
leave-from-class="opacity-100 scale-100"
|
|
69
|
+
leave-to-class="opacity-0 scale-95"
|
|
70
|
+
>
|
|
71
|
+
<div v-for="toast in centerToasts" :key="toast.id" class="pointer-events-auto">
|
|
72
|
+
<SparkAlert
|
|
73
|
+
:type="toast.type"
|
|
74
|
+
:closeable="toast.closeable"
|
|
75
|
+
@close="handleClose(toast.id)"
|
|
76
|
+
@mouseenter="handleMouseEnter(toast.id)"
|
|
77
|
+
@mouseleave="handleMouseLeave(toast.id)"
|
|
78
|
+
>
|
|
79
|
+
<component
|
|
80
|
+
v-if="toast.component"
|
|
81
|
+
:is="toast.component"
|
|
82
|
+
v-bind="toast.props"
|
|
83
|
+
/>
|
|
84
|
+
<template v-else>{{ toast.message }}</template>
|
|
85
|
+
</SparkAlert>
|
|
86
|
+
</div>
|
|
87
|
+
</TransitionGroup>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<!-- Bottom Left -->
|
|
91
|
+
<div class="fixed bottom-4 left-4 z-[2000] flex flex-col-reverse gap-3 max-w-sm w-full pointer-events-none">
|
|
92
|
+
<TransitionGroup
|
|
93
|
+
enter-active-class="transition-all duration-300 ease-out"
|
|
94
|
+
enter-from-class="opacity-0 -translate-x-4"
|
|
95
|
+
enter-to-class="opacity-100 translate-x-0"
|
|
96
|
+
leave-active-class="transition-all duration-200 ease-in"
|
|
97
|
+
leave-from-class="opacity-100 translate-x-0"
|
|
98
|
+
leave-to-class="opacity-0 -translate-x-4"
|
|
99
|
+
>
|
|
100
|
+
<div v-for="toast in bottomLeftToasts" :key="toast.id" class="pointer-events-auto">
|
|
101
|
+
<SparkAlert
|
|
102
|
+
:type="toast.type"
|
|
103
|
+
:closeable="toast.closeable"
|
|
104
|
+
@close="handleClose(toast.id)"
|
|
105
|
+
@mouseenter="handleMouseEnter(toast.id)"
|
|
106
|
+
@mouseleave="handleMouseLeave(toast.id)"
|
|
107
|
+
>
|
|
108
|
+
<component
|
|
109
|
+
v-if="toast.component"
|
|
110
|
+
:is="toast.component"
|
|
111
|
+
v-bind="toast.props"
|
|
112
|
+
/>
|
|
113
|
+
<template v-else>{{ toast.message }}</template>
|
|
114
|
+
</SparkAlert>
|
|
115
|
+
</div>
|
|
116
|
+
</TransitionGroup>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Bottom Right -->
|
|
120
|
+
<div class="fixed bottom-4 right-4 z-[2000] flex flex-col-reverse gap-3 max-w-sm w-full pointer-events-none">
|
|
121
|
+
<TransitionGroup
|
|
122
|
+
enter-active-class="transition-all duration-300 ease-out"
|
|
123
|
+
enter-from-class="opacity-0 translate-x-4"
|
|
124
|
+
enter-to-class="opacity-100 translate-x-0"
|
|
125
|
+
leave-active-class="transition-all duration-200 ease-in"
|
|
126
|
+
leave-from-class="opacity-100 translate-x-0"
|
|
127
|
+
leave-to-class="opacity-0 translate-x-4"
|
|
128
|
+
>
|
|
129
|
+
<div v-for="toast in bottomRightToasts" :key="toast.id" class="pointer-events-auto">
|
|
130
|
+
<SparkAlert
|
|
131
|
+
:type="toast.type"
|
|
132
|
+
:closeable="toast.closeable"
|
|
133
|
+
@close="handleClose(toast.id)"
|
|
134
|
+
@mouseenter="handleMouseEnter(toast.id)"
|
|
135
|
+
@mouseleave="handleMouseLeave(toast.id)"
|
|
136
|
+
>
|
|
137
|
+
<component
|
|
138
|
+
v-if="toast.component"
|
|
139
|
+
:is="toast.component"
|
|
140
|
+
v-bind="toast.props"
|
|
141
|
+
/>
|
|
142
|
+
<template v-else>{{ toast.message }}</template>
|
|
143
|
+
</SparkAlert>
|
|
144
|
+
</div>
|
|
145
|
+
</TransitionGroup>
|
|
146
|
+
</div>
|
|
147
|
+
</Teleport>
|
|
148
|
+
</template>
|
|
149
|
+
|
|
150
|
+
<script setup>
|
|
151
|
+
import { computed } from 'vue'
|
|
152
|
+
import { sparkNotificationService } from '@/composables/sparkNotificationService'
|
|
153
|
+
import SparkAlert from '@/components/SparkAlert.vue'
|
|
154
|
+
|
|
155
|
+
const toastState = sparkNotificationService.toastState
|
|
156
|
+
|
|
157
|
+
const topLeftToasts = computed(() =>
|
|
158
|
+
toastState.toasts.filter(t => t.position === 'top-left')
|
|
159
|
+
)
|
|
160
|
+
const topRightToasts = computed(() =>
|
|
161
|
+
toastState.toasts.filter(t => t.position === 'top-right')
|
|
162
|
+
)
|
|
163
|
+
const centerToasts = computed(() =>
|
|
164
|
+
toastState.toasts.filter(t => t.position === 'center')
|
|
165
|
+
)
|
|
166
|
+
const bottomLeftToasts = computed(() =>
|
|
167
|
+
toastState.toasts.filter(t => t.position === 'bottom-left')
|
|
168
|
+
)
|
|
169
|
+
const bottomRightToasts = computed(() =>
|
|
170
|
+
toastState.toasts.filter(t => t.position === 'bottom-right')
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const handleClose = (toastId) => {
|
|
174
|
+
sparkNotificationService.hideToast(toastId)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const handleMouseEnter = (toastId) => {
|
|
178
|
+
sparkNotificationService.pauseToast(toastId)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const handleMouseLeave = (toastId) => {
|
|
182
|
+
sparkNotificationService.resumeToast(toastId)
|
|
183
|
+
}
|
|
184
|
+
</script>
|
package/src/components/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { default as SparkAlert } from './SparkAlert.vue'
|
|
2
2
|
export { default as SparkAppSelector } from './SparkAppSelector.vue'
|
|
3
|
+
export { default as SparkNotificationOutlet } from './SparkNotificationOutlet.vue'
|
|
4
|
+
export { default as SparkToastContainer } from './SparkToastContainer.vue'
|
|
3
5
|
export { default as SparkBrandSelector } from './SparkBrandSelector.vue'
|
|
4
6
|
export { default as SparkButton } from './SparkButton.vue'
|
|
5
7
|
export { default as SparkButtonGroup } from './SparkButtonGroup.vue'
|
package/src/composables/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { sparkModalService } from './sparkModalService.js'
|
|
2
|
+
export { sparkNotificationService } from './sparkNotificationService.js'
|
|
2
3
|
export { sparkOverlayService } from './sparkOverlayService.js'
|
|
3
4
|
export { useSparkOverlay } from './useSparkOverlay.js'
|
|
4
5
|
export { useSparkTableRouteSync } from './useSparkTableRouteSync.js'
|