@usssa/component-library 1.0.0-alpha.243 → 1.0.0-alpha.245
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/README.md
CHANGED
package/package.json
CHANGED
|
@@ -73,6 +73,7 @@ const bracketSides = [
|
|
|
73
73
|
]
|
|
74
74
|
const nodeHeight = 40
|
|
75
75
|
const nodeWidth = 240
|
|
76
|
+
|
|
76
77
|
const self = 'bottom middle'
|
|
77
78
|
const svgWidth = 1400
|
|
78
79
|
|
|
@@ -80,6 +81,12 @@ const activeRound = ref({ roundNo: 1, columnNo: 3 })
|
|
|
80
81
|
const bracketSheet = ref([])
|
|
81
82
|
const currentTransform = ref(d3.zoomIdentity)
|
|
82
83
|
const gRef = ref(null)
|
|
84
|
+
const layoutCache = ref({
|
|
85
|
+
contentWidth: 0,
|
|
86
|
+
contentHeight: 0,
|
|
87
|
+
maxRow: 0,
|
|
88
|
+
numColumns: 0,
|
|
89
|
+
})
|
|
83
90
|
const nodeGridPositions = ref({})
|
|
84
91
|
const openMenu = ref(false)
|
|
85
92
|
const openRoundMenu = ref(false)
|
|
@@ -225,13 +232,12 @@ const cancelTeamSheetSelection = () => {
|
|
|
225
232
|
tempSelectedTeamId.value = selectedTeamId.value
|
|
226
233
|
}
|
|
227
234
|
|
|
228
|
-
// Compute all node positions
|
|
235
|
+
// Compute all node positions and cache layout values
|
|
229
236
|
const computeBracketLayout = () => {
|
|
230
237
|
const newPositions = {}
|
|
231
238
|
|
|
232
239
|
// Scan through all games to find every node with an explicit position
|
|
233
240
|
games.value.forEach((game) => {
|
|
234
|
-
// 1. Position the source teams for the game
|
|
235
241
|
game.team.forEach((team) => {
|
|
236
242
|
if (typeof team === 'object' && team.teamName && team.position) {
|
|
237
243
|
const compositeId = `${team.teamName}_${team.position.columnNo}_${team.position.rowNo}`
|
|
@@ -241,8 +247,6 @@ const computeBracketLayout = () => {
|
|
|
241
247
|
}
|
|
242
248
|
}
|
|
243
249
|
})
|
|
244
|
-
|
|
245
|
-
// 2. Position the destination (winner) node for the game
|
|
246
250
|
if (
|
|
247
251
|
game.winnerDestination &&
|
|
248
252
|
typeof game.winnerDestination === 'object' &&
|
|
@@ -261,8 +265,6 @@ const computeBracketLayout = () => {
|
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
267
|
}
|
|
264
|
-
|
|
265
|
-
// 3. Position the loser destination node for the game
|
|
266
268
|
if (
|
|
267
269
|
game.loserDestination &&
|
|
268
270
|
typeof game.loserDestination === 'object' &&
|
|
@@ -282,8 +284,6 @@ const computeBracketLayout = () => {
|
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
286
|
})
|
|
285
|
-
|
|
286
|
-
// Add winnerPlace and loserPlace nodes from each round
|
|
287
287
|
props.bracketData.round.forEach((round) => {
|
|
288
288
|
if (Array.isArray(round.winnerPlace)) {
|
|
289
289
|
round.winnerPlace.forEach((place) => {
|
|
@@ -308,8 +308,23 @@ const computeBracketLayout = () => {
|
|
|
308
308
|
})
|
|
309
309
|
}
|
|
310
310
|
})
|
|
311
|
-
|
|
312
311
|
nodeGridPositions.value = newPositions
|
|
312
|
+
|
|
313
|
+
// Cache layout values
|
|
314
|
+
const uniqueColumns = [
|
|
315
|
+
...new Set(props.bracketData.round.map((r) => r.columnNo)),
|
|
316
|
+
]
|
|
317
|
+
const numColumns = uniqueColumns.length
|
|
318
|
+
let maxRow = 0
|
|
319
|
+
Object.values(newPositions).forEach(({ row }) => {
|
|
320
|
+
if (row > maxRow) maxRow = row
|
|
321
|
+
})
|
|
322
|
+
layoutCache.value.numColumns = numColumns
|
|
323
|
+
layoutCache.value.maxRow = maxRow
|
|
324
|
+
layoutCache.value.contentWidth =
|
|
325
|
+
50 + (numColumns - 1) * props.hGap + nodeWidth + 50
|
|
326
|
+
layoutCache.value.contentHeight =
|
|
327
|
+
50 + (maxRow - 1) * props.vGap + nodeHeight + 50
|
|
313
328
|
}
|
|
314
329
|
|
|
315
330
|
const descriptiveRoundName = (round) => {
|
|
@@ -656,7 +671,7 @@ const renderBracket = () => {
|
|
|
656
671
|
nodeGroups
|
|
657
672
|
.append('text')
|
|
658
673
|
.attr('class', 'node-label')
|
|
659
|
-
.attr('x', (d) => d.x +
|
|
674
|
+
.attr('x', (d) => d.x + (seedTeamIds.includes(d.id) ? 50 : 40)) // Move label right only for seed teams
|
|
660
675
|
.attr('y', (d) => d.y + nodeHeight / 2 + 5)
|
|
661
676
|
.attr('text-anchor', (d) =>
|
|
662
677
|
seedTeamIds.includes(d.id) ? 'start' : 'middle'
|
|
@@ -877,6 +892,38 @@ const renderBracket = () => {
|
|
|
877
892
|
})
|
|
878
893
|
}
|
|
879
894
|
|
|
895
|
+
function resetZoom() {
|
|
896
|
+
const svg = d3.select(svgRef.value)
|
|
897
|
+
if (zoomRef.value && svgRef.value) {
|
|
898
|
+
const svgElement = svgRef.value
|
|
899
|
+
const { width, height } = svgElement.getBoundingClientRect()
|
|
900
|
+
const centerX = width / 2
|
|
901
|
+
const centerY = height / 2
|
|
902
|
+
|
|
903
|
+
const k = 1
|
|
904
|
+
const contentWidth = layoutCache.value.contentWidth
|
|
905
|
+
const contentHeight = layoutCache.value.contentHeight
|
|
906
|
+
const viewportWidth = svgElement.clientWidth
|
|
907
|
+
const viewportHeight = svgElement.clientHeight
|
|
908
|
+
const minTx = viewportWidth - contentWidth * k
|
|
909
|
+
const maxTx = 0
|
|
910
|
+
const minTy = viewportHeight - contentHeight * k
|
|
911
|
+
const maxTy = 0
|
|
912
|
+
let tx = centerX
|
|
913
|
+
let ty = centerY
|
|
914
|
+
tx = minTx > 0 ? minTx / 2 : Math.max(minTx, Math.min(maxTx, tx))
|
|
915
|
+
ty = minTy > 0 ? minTy / 2 : Math.max(minTy, Math.min(maxTy, ty))
|
|
916
|
+
const clampedTransform = d3.zoomIdentity.translate(tx, ty).scale(k)
|
|
917
|
+
svg
|
|
918
|
+
.transition()
|
|
919
|
+
.duration(400)
|
|
920
|
+
.ease(d3.easeCubic)
|
|
921
|
+
.call(zoomRef.value.transform, clampedTransform)
|
|
922
|
+
d3.select(gRef.value).attr('transform', clampedTransform)
|
|
923
|
+
currentTransform.value = clampedTransform
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
880
927
|
const scrollToBracketSide = (side) => {
|
|
881
928
|
if (!props.bracketData || !props.bracketData.round) return
|
|
882
929
|
|
|
@@ -953,6 +1000,8 @@ const setSelectedTeamId = (teamId) => {
|
|
|
953
1000
|
const team = toolbarTeams.value.find((t) => t.id === teamId)
|
|
954
1001
|
if (team) {
|
|
955
1002
|
selectedTeamId.value = team.id
|
|
1003
|
+
// Reset zoom to initial scale and center
|
|
1004
|
+
resetZoom()
|
|
956
1005
|
}
|
|
957
1006
|
openMenu.value = false
|
|
958
1007
|
}
|
|
@@ -975,31 +1024,57 @@ const toggleRoundInSheet = (round) => {
|
|
|
975
1024
|
|
|
976
1025
|
const zoomIn = () => {
|
|
977
1026
|
const svg = d3.select(svgRef.value)
|
|
978
|
-
if (zoomRef.value) {
|
|
1027
|
+
if (zoomRef.value && svgRef.value) {
|
|
1028
|
+
const svgElement = svgRef.value
|
|
1029
|
+
const { width, height } = svgElement.getBoundingClientRect()
|
|
1030
|
+
const centerX = width / 2
|
|
1031
|
+
const centerY = height / 2
|
|
1032
|
+
|
|
979
1033
|
// Increase scale by 20%
|
|
980
|
-
const
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1034
|
+
const prevScale = currentTransform.value.k || 1
|
|
1035
|
+
const newScale = Math.min(prevScale * 1.2, 2.5)
|
|
1036
|
+
|
|
1037
|
+
// Calculate new translation to keep center in view
|
|
1038
|
+
const newX =
|
|
1039
|
+
centerX - (centerX - currentTransform.value.x) * (newScale / prevScale)
|
|
1040
|
+
const newY =
|
|
1041
|
+
centerY - (centerY - currentTransform.value.y) * (newScale / prevScale)
|
|
1042
|
+
|
|
1043
|
+
const newTransform = d3.zoomIdentity.translate(newX, newY).scale(newScale)
|
|
1044
|
+
|
|
1045
|
+
svg
|
|
1046
|
+
.transition()
|
|
1047
|
+
.duration(200)
|
|
1048
|
+
.ease(d3.easeCubic)
|
|
1049
|
+
.call(zoomRef.value.transform, newTransform)
|
|
988
1050
|
}
|
|
989
1051
|
}
|
|
990
1052
|
|
|
991
1053
|
const zoomOut = () => {
|
|
992
1054
|
const svg = d3.select(svgRef.value)
|
|
993
|
-
if (zoomRef.value) {
|
|
1055
|
+
if (zoomRef.value && svgRef.value) {
|
|
1056
|
+
const svgElement = svgRef.value
|
|
1057
|
+
const { width, height } = svgElement.getBoundingClientRect()
|
|
1058
|
+
const centerX = width / 2
|
|
1059
|
+
const centerY = height / 2
|
|
1060
|
+
|
|
994
1061
|
// Decrease scale by 20%
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1062
|
+
const prevScale = currentTransform.value.k || 1
|
|
1063
|
+
const newScale = Math.max(prevScale / 1.2, 0.5)
|
|
1064
|
+
|
|
1065
|
+
// Calculate new translation to keep center in view
|
|
1066
|
+
const newX =
|
|
1067
|
+
centerX - (centerX - currentTransform.value.x) * (newScale / prevScale)
|
|
1068
|
+
const newY =
|
|
1069
|
+
centerY - (centerY - currentTransform.value.y) * (newScale / prevScale)
|
|
1070
|
+
|
|
1071
|
+
const newTransform = d3.zoomIdentity.translate(newX, newY).scale(newScale)
|
|
1072
|
+
|
|
1073
|
+
svg
|
|
1074
|
+
.transition()
|
|
1075
|
+
.duration(200)
|
|
1076
|
+
.ease(d3.easeCubic)
|
|
1077
|
+
.call(zoomRef.value.transform, newTransform)
|
|
1003
1078
|
}
|
|
1004
1079
|
}
|
|
1005
1080
|
|
|
@@ -1075,11 +1150,39 @@ onMounted(() => {
|
|
|
1075
1150
|
const zoomBehavior = d3
|
|
1076
1151
|
.zoom()
|
|
1077
1152
|
.scaleExtent([0.5, 2.5])
|
|
1078
|
-
//
|
|
1079
|
-
// while allowing other events like mouse clicks to pass through.
|
|
1153
|
+
// Custom filter to intercept wheel events and always zoom centered
|
|
1080
1154
|
.filter((event) => {
|
|
1081
1155
|
if (event.type === 'wheel') {
|
|
1082
1156
|
event.preventDefault()
|
|
1157
|
+
// Manually handle wheel zoom centered on SVG
|
|
1158
|
+
const svgElement = svgRef.value
|
|
1159
|
+
const { width, height } = svgElement.getBoundingClientRect()
|
|
1160
|
+
const centerX = width / 2
|
|
1161
|
+
const centerY = height / 2
|
|
1162
|
+
const prevScale = currentTransform.value.k || 1
|
|
1163
|
+
let newScale = prevScale
|
|
1164
|
+
// D3 uses event.deltaY for wheel direction
|
|
1165
|
+
if (event.deltaY < 0) {
|
|
1166
|
+
newScale = Math.min(prevScale * 1.2, 2.5)
|
|
1167
|
+
} else {
|
|
1168
|
+
newScale = Math.max(prevScale / 1.2, 0.5)
|
|
1169
|
+
}
|
|
1170
|
+
// Calculate new translation to keep center in view
|
|
1171
|
+
const newX =
|
|
1172
|
+
centerX -
|
|
1173
|
+
(centerX - currentTransform.value.x) * (newScale / prevScale)
|
|
1174
|
+
const newY =
|
|
1175
|
+
centerY -
|
|
1176
|
+
(centerY - currentTransform.value.y) * (newScale / prevScale)
|
|
1177
|
+
const newTransform = d3.zoomIdentity
|
|
1178
|
+
.translate(newX, newY)
|
|
1179
|
+
.scale(newScale)
|
|
1180
|
+
d3.select(svgRef.value)
|
|
1181
|
+
.transition()
|
|
1182
|
+
.duration(200)
|
|
1183
|
+
.ease(d3.easeCubic)
|
|
1184
|
+
.call(zoomBehavior.transform, newTransform)
|
|
1185
|
+
return false // Prevent default D3 wheel zoom
|
|
1083
1186
|
}
|
|
1084
1187
|
// Allow click-and-drag panning
|
|
1085
1188
|
return !event.ctrlKey || event.type === 'wheel'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref } from 'vue'
|
|
2
|
+
import { nextTick, ref, watch } from 'vue'
|
|
3
3
|
import URadioStd from './URadioStd.vue'
|
|
4
|
+
import UTooltip from './UTooltip.vue'
|
|
4
5
|
|
|
5
6
|
const emit = defineEmits(['handleSelectChange'])
|
|
6
7
|
const isSelected = defineModel('isSelected', {
|
|
@@ -71,6 +72,17 @@ const props = defineProps({
|
|
|
71
72
|
})
|
|
72
73
|
|
|
73
74
|
const isExpanded = ref(false)
|
|
75
|
+
const isOverflowing = ref(false)
|
|
76
|
+
const labelRef = ref(null)
|
|
77
|
+
|
|
78
|
+
const checkOverflow = () => {
|
|
79
|
+
nextTick(() => {
|
|
80
|
+
if (labelRef.value) {
|
|
81
|
+
const el = labelRef.value
|
|
82
|
+
isOverflowing.value = el.scrollWidth > el.clientWidth
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
74
86
|
|
|
75
87
|
const handleChange = (value) => {
|
|
76
88
|
emit('handleSelectChange', value)
|
|
@@ -89,6 +101,16 @@ const toggleExpansion = () => {
|
|
|
89
101
|
isExpanded.value = !isExpanded.value
|
|
90
102
|
}
|
|
91
103
|
}
|
|
104
|
+
|
|
105
|
+
watch(
|
|
106
|
+
() => props.label,
|
|
107
|
+
() => {
|
|
108
|
+
checkOverflow()
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
immediate: true,
|
|
112
|
+
}
|
|
113
|
+
)
|
|
92
114
|
</script>
|
|
93
115
|
|
|
94
116
|
<template>
|
|
@@ -139,7 +161,20 @@ const toggleExpansion = () => {
|
|
|
139
161
|
</q-icon>
|
|
140
162
|
<slot name="avatar" />
|
|
141
163
|
<div class="column u-expansion-label">
|
|
142
|
-
<span
|
|
164
|
+
<span
|
|
165
|
+
:class="`text-caption-lg text-truncate ${
|
|
166
|
+
isOverflowing ? `cursor-pointer` : ``
|
|
167
|
+
}`"
|
|
168
|
+
ref="labelRef"
|
|
169
|
+
@mouseenter="checkOverflow"
|
|
170
|
+
>{{ label }}</span
|
|
171
|
+
>
|
|
172
|
+
<UTooltip
|
|
173
|
+
v-if="isOverflowing"
|
|
174
|
+
anchor="bottom middle"
|
|
175
|
+
:description="label"
|
|
176
|
+
:offset="[14, 14]"
|
|
177
|
+
/>
|
|
143
178
|
<span class="text-body-xs">{{ caption }}</span>
|
|
144
179
|
</div>
|
|
145
180
|
<q-icon
|
|
@@ -209,7 +244,20 @@ const toggleExpansion = () => {
|
|
|
209
244
|
</q-icon>
|
|
210
245
|
<slot name="avatar" />
|
|
211
246
|
<div class="column u-expansion-label">
|
|
212
|
-
<span
|
|
247
|
+
<span
|
|
248
|
+
:class="`text-caption-lg text-truncate ${
|
|
249
|
+
isOverflowing ? `cursor-pointer` : ``
|
|
250
|
+
}`"
|
|
251
|
+
ref="labelRef"
|
|
252
|
+
@mouseenter="checkOverflow"
|
|
253
|
+
>{{ label }}</span
|
|
254
|
+
>
|
|
255
|
+
<UTooltip
|
|
256
|
+
v-if="isOverflowing"
|
|
257
|
+
anchor="bottom middle"
|
|
258
|
+
:description="label"
|
|
259
|
+
:offset="[14, 14]"
|
|
260
|
+
/>
|
|
213
261
|
<span class="text-body-xs text-description">{{ caption }}</span>
|
|
214
262
|
</div>
|
|
215
263
|
<q-icon
|
|
@@ -254,6 +302,13 @@ const toggleExpansion = () => {
|
|
|
254
302
|
word-break: break-word
|
|
255
303
|
overflow-wrap: break-word
|
|
256
304
|
|
|
305
|
+
.text-truncate
|
|
306
|
+
overflow: hidden
|
|
307
|
+
text-overflow: ellipsis
|
|
308
|
+
white-space: nowrap
|
|
309
|
+
display: block
|
|
310
|
+
max-width: 100%
|
|
311
|
+
|
|
257
312
|
.q-expansion-item__content > :first-child
|
|
258
313
|
padding: $xs $ba $ba $ba
|
|
259
314
|
|
|
@@ -1671,6 +1671,11 @@ window.addEventListener('resize', handleCellOverflowResize)
|
|
|
1671
1671
|
:color="props?.row?.iconColor ?? 'primary'"
|
|
1672
1672
|
size="1.5rem"
|
|
1673
1673
|
/>
|
|
1674
|
+
<div v-if="typeof col?.colorMap === 'function'" class="flex items-baseline" :style="{ gap: '0.25rem',}">
|
|
1675
|
+
<span :class="`table-text-colormap-${col?.colorMap(props.row)?.color} text-caption-md`"> {{ col?.colorMap(props.row)?.value }}</span>
|
|
1676
|
+
<span class="text-body-sm">{{ props.row[col.field] }}</span>
|
|
1677
|
+
</div>
|
|
1678
|
+
|
|
1674
1679
|
<!-- Text -->
|
|
1675
1680
|
<span v-else class="text-body-sm">{{ props.row[col.field] }}</span>
|
|
1676
1681
|
</div>
|
|
@@ -738,8 +738,11 @@ watch(
|
|
|
738
738
|
col.combineColoumn(props.row)
|
|
739
739
|
? ' combine-col-wrapper'
|
|
740
740
|
: ''
|
|
741
|
-
}`"
|
|
741
|
+
} ${typeof col?.colorMap === 'function' ? `combine-col-wrapper`: ``}`"
|
|
742
742
|
>
|
|
743
|
+
<span v-if="typeof col?.colorMap === 'function'" :class="`table-text-colormap-${col?.colorMap(props.row)?.color} text-caption-md`">
|
|
744
|
+
{{ col?.colorMap(props.row)?.value }}
|
|
745
|
+
</span>
|
|
743
746
|
<div v-if="col.type !== 'icon'">
|
|
744
747
|
{{ props.row[col.field] }}
|
|
745
748
|
</div>
|
|
@@ -1691,4 +1694,7 @@ watch(
|
|
|
1691
1694
|
white-space: nowrap
|
|
1692
1695
|
display: block
|
|
1693
1696
|
|
|
1697
|
+
.table-text-colormap-positive
|
|
1698
|
+
color: $green-6 !important
|
|
1699
|
+
|
|
1694
1700
|
</style>
|