1
1
<template >
2
- <div
3
- ref =" gridContainer"
4
- class =" grid-stack"
5
- >
2
+ <div class =" grid-wrapper" >
6
3
<div
7
- v-for =" tile in props.tiles"
8
- :key =" tile.id"
9
- class =" grid-stack-item"
10
- :data-id =" `${tile.id}`"
11
- :data-testid =" `grid-stack-item-${tile.id}`"
12
- :gs-h =" tile.layout.size.rows"
13
- :gs-lazy-load =" true"
14
- :gs-w =" tile.layout.size.cols"
15
- :gs-x =" tile.layout.position.col"
16
- :gs-y =" tile.layout.position.row"
4
+ ref =" gridContainer"
5
+ class =" grid-stack"
17
6
>
18
- <div class =" grid-stack-item-content" >
19
- <slot
20
- name =" tile"
21
- :tile =" tile"
22
- />
7
+ <div
8
+ v-for =" tile in props.tiles"
9
+ :key =" tile.id"
10
+ class =" grid-stack-item"
11
+ :data-id =" `${tile.id}`"
12
+ :data-testid =" `grid-stack-item-${tile.id}`"
13
+ :gs-h =" rowToGridStack(tile.layout.size.rows)"
14
+ :gs-lazy-load =" true"
15
+ :gs-size-to-content =" !editable"
16
+ :gs-w =" tile.layout.size.cols"
17
+ :gs-x =" tile.layout.position.col"
18
+ :gs-y =" rowToGridStack(tile.layout.position.row)"
19
+ >
20
+ <div class =" grid-stack-item-content" >
21
+ <slot
22
+ name =" tile"
23
+ :tile =" tile"
24
+ />
25
+ </div >
23
26
</div >
24
27
</div >
25
28
</div >
26
29
</template >
27
30
28
31
<script lang='ts' setup generic =" T " >
29
- import { onMounted , onUnmounted , ref , watch , nextTick , watchEffect } from ' vue'
32
+ import { onMounted , onUnmounted , computed , ref , watch , nextTick , watchEffect } from ' vue'
30
33
import { GridStack } from ' gridstack'
31
34
import type { GridStackNode } from ' gridstack'
32
- import type { GridSize , GridTile } from ' src/types'
35
+ import type { GridTile } from ' src/types'
33
36
import ' gridstack/dist/gridstack.min.css'
34
37
import ' gridstack/dist/gridstack-extra.min.css'
38
+ import { DEFAULT_TILE_HEIGHT , MIN_CELL_HEIGHT } from ' ../../constants'
39
+ import { useDebounce } from ' @kong-ui-public/core'
35
40
36
41
export type DraggableGridLayoutExpose <T > = {
37
42
removeWidget: (id : number | string ) => void
43
+ redraw: () => void
38
44
tiles: GridTile <T >[]
39
- gridSize: GridSize
40
45
}
41
46
42
47
const props = withDefaults (defineProps <{
43
48
tiles: GridTile <T >[]
44
- gridSize: GridSize
45
49
tileHeight? : number
50
+ editable? : boolean
46
51
}>(), {
47
- tileHeight: 200 ,
52
+ tileHeight: DEFAULT_TILE_HEIGHT ,
48
53
})
54
+
55
+ const rowToGridStack = (row ) => props .editable ? row : row * Math .floor (props .tileHeight / MIN_CELL_HEIGHT )
56
+ const gridStackToRow = (gridRow ) => props .editable ? gridRow : Math .floor (gridRow / Math .floor (props .tileHeight / MIN_CELL_HEIGHT ))
57
+
49
58
const emit = defineEmits <{
50
59
(e : ' update-tiles' , tiles : GridTile <T >[]): void
51
60
}>()
52
61
62
+ const headerCursor = computed (() => props .editable ? ' move' : ' default' )
63
+
53
64
const gridContainer = ref <HTMLDivElement | null >(null )
54
65
55
66
const tilesRef = ref <Map <string , GridTile <any >>>(new Map (props .tiles .map (t => [` ${t .id } ` , t ])))
@@ -67,8 +78,8 @@ const makeTilesFromGridItems = (items: GridStackNode[]) => {
67
78
return {
68
79
... tile ,
69
80
layout: {
70
- position: { col: Number (item .x ), row: Number (item .y ) },
71
- size: { cols: Number (item .w ), rows: Number (item .h ) },
81
+ position: { col: Number (item .x ), row: gridStackToRow ( Number (item .y ) ) },
82
+ size: { cols: Number (item .w ), rows: gridStackToRow ( Number (item .h ) ) },
72
83
},
73
84
} satisfies GridTile <T >
74
85
}
@@ -92,18 +103,36 @@ const removeHandler = (_: Event, items: GridStackNode[]) => {
92
103
emit (' update-tiles' , Array .from (tilesRef .value .values ()))
93
104
}
94
105
95
- onMounted (() => {
106
+ onMounted (async () => {
96
107
if (gridContainer .value ) {
97
108
grid = GridStack .init ({
98
- column: props .gridSize .cols ,
99
- cellHeight: props .tileHeight ,
109
+ margin: 10 ,
110
+ column: 6 ,
111
+ cellHeight: props .editable ? props .tileHeight : MIN_CELL_HEIGHT ,
112
+ cellHeightThrottle: 100 ,
100
113
resizable: { handles: ' se, sw' },
101
114
handle: ' .tile-header' ,
102
-
115
+ staticGrid: ! props .editable ,
116
+ columnOpts: ! props .editable ? {
117
+ breakpointForWindow: true ,
118
+ columnMax: 6 ,
119
+ breakpoints: [{
120
+ w: 640 , c: 1 , // mobile
121
+ }, {
122
+ w: 1024 , c: 2 , // tablet
123
+ }, {
124
+ w: 1280 , c: 4 , // laptop
125
+ }, {
126
+ w: 1536 , c: 6 , // desktop
127
+ }],
128
+ } : {},
103
129
}, gridContainer .value )
104
- grid .on (' change' , updateTiles )
105
- grid .on (' added' , updateTiles )
106
- grid .on (' removed' , removeHandler )
130
+
131
+ if (props .editable ) {
132
+ grid .on (' change' , updateTiles )
133
+ grid .on (' added' , updateTiles )
134
+ grid .on (' removed' , removeHandler )
135
+ }
107
136
}
108
137
})
109
138
@@ -113,6 +142,16 @@ onUnmounted(() => {
113
142
}
114
143
})
115
144
145
+ const { debounce } = useDebounce ()
146
+
147
+ const debouncedRedraw = debounce (() => {
148
+ props .tiles .forEach (({ id }) => {
149
+ const el = gridContainer .value .querySelector (makeSelector (id )) as HTMLElement
150
+ grid .resizeToContent (el )
151
+ grid .compact ()
152
+ })
153
+ }, 100 )
154
+
116
155
const removeWidget = (id : number | string ) => {
117
156
if (grid && gridContainer .value ) {
118
157
const el = gridContainer .value .querySelector (makeSelector (id )) as HTMLElement
@@ -131,7 +170,7 @@ watch(() => props.tiles.length, async (newLen, oldLen) => {
131
170
grid .makeWidget (makeSelector (tile .id ), {
132
171
autoPosition: true ,
133
172
w: tile .layout .size .cols ,
134
- h: tile .layout .size .rows ,
173
+ h: rowToGridStack ( tile .layout .size .rows ) ,
135
174
})
136
175
}
137
176
}
@@ -144,14 +183,30 @@ watchEffect(() => {
144
183
})
145
184
})
146
185
147
- defineExpose ({ removeWidget })
186
+ defineExpose ({ removeWidget , redraw: debouncedRedraw })
148
187
149
188
</script >
150
189
151
190
<style lang="scss" scoped>
191
+ .grid-wrapper {
192
+ /*
193
+ * If you want space between your items in GridStack, you have to provide a
194
+ * margin. If you provide a margin, GridStack forces you to have an outer
195
+ * margin not just between tiles. So, anything using this component is always
196
+ * 20 pixels (2 * margin size) shorter in both the x and y axis than the space
197
+ * that's provided. To solve this we can wrap it with a basic div and give the
198
+ * GridStack container negative margin. That makes it so content actually fits
199
+ * the intended layout of anything that uses this component.
200
+ */
201
+ .grid-stack {
202
+ margin : -10px ;
203
+ }
204
+ }
205
+
152
206
:deep(.tile-header ) {
153
- cursor : move ;
207
+ cursor : v-bind ( ' headerCursor ' ) ;
154
208
}
209
+
155
210
$rotate-values : (
156
211
' se' : 0deg ,
157
212
' sw' : 90deg ,
0 commit comments