Skip to content

Commit 0a0555d

Browse files
Merge pull request #485 from aziontech/feat/ENG-37079-datatable-ds-components
[ENG-37079] feat: add DataTable DS component
2 parents d378a75 + 0cea5e2 commit 0a0555d

36 files changed

Lines changed: 3437 additions & 1 deletion

apps/storybook/src/stories/components/DataTable.stories.js

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.

packages/webkit/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@
131131
"./svg/azion/technologies": "./src/svg/azion/technologies/technologies.vue",
132132
"./svg/azion/default": "./src/svg/azion/default/default.vue",
133133
"./svg/azion/min": "./src/svg/azion/min/min.vue",
134-
"./styles/country-flags": "./src/styles/country-flags.css"
134+
"./styles/country-flags": "./src/styles/country-flags.css",
135+
"./data-table": "./src/components/data-table/index.js",
136+
"./data-table/composables": "./src/components/data-table/composables/index.js",
137+
"./data-table/types": "./src/components/data-table/types/index.js"
135138
}
136139
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export { useRowEditing } from './useRowEditing'
2+
export { useRowOrdering } from './useRowOrdering'
3+
export { useRowSelection } from './useRowSelection'
4+
export { useFrozenColumns } from './useFrozenColumns'
5+
export { useViewMode } from './useViewMode'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function useFrozenColumns(options) {
2+
const { frozenColumns } = options
3+
4+
function isFrozen(field) {
5+
return frozenColumns.includes(field)
6+
}
7+
8+
function shouldNavigateOnClick(field) {
9+
return isFrozen(field)
10+
}
11+
12+
return {
13+
isFrozen,
14+
shouldNavigateOnClick
15+
}
16+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ref, computed } from 'vue'
2+
3+
export function useRowEditing(options = {}) {
4+
const dataKey = options.dataKey ?? 'id'
5+
const editingRows = ref([])
6+
const backups = new Map()
7+
8+
function startEdit(row) {
9+
const key = row[dataKey]
10+
backups.set(key, { ...row })
11+
if (!editingRows.value.includes(row)) {
12+
editingRows.value = [...editingRows.value, row]
13+
}
14+
}
15+
16+
function saveEdit(event) {
17+
const key = event.data[dataKey]
18+
const originalData = backups.get(key) || { ...event.data }
19+
backups.delete(key)
20+
editingRows.value = editingRows.value.filter((r) => r[dataKey] !== key)
21+
return { newData: event.newData, originalData, index: event.index }
22+
}
23+
24+
function cancelEdit(event) {
25+
const key = event.data[dataKey]
26+
const restoredData = backups.get(key) || { ...event.data }
27+
backups.delete(key)
28+
editingRows.value = editingRows.value.filter((r) => r[dataKey] !== key)
29+
return { restoredData, index: event.index }
30+
}
31+
32+
function cancelAll() {
33+
backups.clear()
34+
editingRows.value = []
35+
}
36+
37+
function isEditing(row) {
38+
return editingRows.value.some((r) => r[dataKey] === row[dataKey])
39+
}
40+
41+
const hasChanges = computed(() => editingRows.value.length > 0)
42+
43+
return {
44+
editingRows,
45+
startEdit,
46+
saveEdit,
47+
cancelEdit,
48+
cancelAll,
49+
isEditing,
50+
hasChanges
51+
}
52+
}
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import { ref, computed } from 'vue'
2+
3+
export function useRowOrdering(options) {
4+
const {
5+
data,
6+
groupColumn,
7+
restrictReorderAcrossGroups = false,
8+
dataKey = 'id',
9+
positionField = 'order',
10+
positionObjectMode = false
11+
} = options
12+
13+
const originalPositions = new Map()
14+
let snapshotTaken = false
15+
16+
function getPositionValue(row) {
17+
if (positionObjectMode) {
18+
return row[positionField]?.value ?? 0
19+
}
20+
return row[positionField] ?? 0
21+
}
22+
23+
function setPositionValue(row, value) {
24+
if (positionObjectMode) {
25+
if (!row[positionField]) {
26+
row[positionField] = { value: 0, immutableValue: 0, altered: false, min: 0, max: 0 }
27+
}
28+
row[positionField].value = value
29+
} else {
30+
row[positionField] = value
31+
}
32+
}
33+
34+
function getImmutableValue(row) {
35+
if (positionObjectMode) {
36+
return row[positionField]?.immutableValue ?? 0
37+
}
38+
return originalPositions.get(row[dataKey]) ?? row[positionField] ?? 0
39+
}
40+
41+
function markAltered(row) {
42+
if (positionObjectMode) {
43+
if (row[positionField]) {
44+
row[positionField].altered =
45+
row[positionField].value !== row[positionField].immutableValue
46+
}
47+
}
48+
}
49+
50+
function takeSnapshot() {
51+
if (!snapshotTaken && !positionObjectMode) {
52+
data.value.forEach((row) => {
53+
originalPositions.set(row[dataKey], row[positionField])
54+
})
55+
snapshotTaken = true
56+
}
57+
}
58+
59+
function getGroupValue(row) {
60+
if (!groupColumn) return undefined
61+
const keys = groupColumn.split('.')
62+
let value = row
63+
for (const key of keys) {
64+
value = value?.[key]
65+
}
66+
return typeof value === 'object' && value !== null ? value.content : value
67+
}
68+
69+
function getGroupItems(groupValue) {
70+
return data.value.filter((r) => getGroupValue(r) === groupValue)
71+
}
72+
73+
function updateGroupPositions(items) {
74+
items.forEach((row, index) => {
75+
setPositionValue(row, index)
76+
if (positionObjectMode && row[positionField]) {
77+
row[positionField].max = items.length - 1
78+
row[positionField].min = 0
79+
markAltered(row)
80+
}
81+
})
82+
}
83+
84+
function onRowReorder(event) {
85+
takeSnapshot()
86+
const { dragIndex, dropIndex } = event
87+
const reordered = [...data.value]
88+
89+
if (restrictReorderAcrossGroups && groupColumn) {
90+
const dragGroup = getGroupValue(reordered[dragIndex])
91+
const dropGroup = getGroupValue(reordered[dropIndex])
92+
if (dragGroup !== dropGroup) {
93+
return { from: dragIndex, to: dropIndex, reorderedData: data.value, blocked: true }
94+
}
95+
}
96+
97+
const [movedItem] = reordered.splice(dragIndex, 1)
98+
reordered.splice(dropIndex, 0, movedItem)
99+
100+
if (restrictReorderAcrossGroups && groupColumn) {
101+
const affectedGroup = getGroupValue(movedItem)
102+
const groupItems = reordered.filter((r) => getGroupValue(r) === affectedGroup)
103+
updateGroupPositions(groupItems)
104+
} else {
105+
reordered.forEach((row, index) => {
106+
setPositionValue(row, positionObjectMode ? index : index + 1)
107+
markAltered(row)
108+
})
109+
}
110+
111+
data.value = reordered
112+
return { from: dragIndex, to: dropIndex, reorderedData: reordered, blocked: false }
113+
}
114+
115+
function changePosition(row, newPosition) {
116+
takeSnapshot()
117+
118+
if (restrictReorderAcrossGroups && groupColumn) {
119+
return changePositionWithinGroup(row, newPosition)
120+
}
121+
122+
const currentIndex = data.value.findIndex((r) => r[dataKey] === row[dataKey])
123+
if (currentIndex === -1) return { reorderedData: data.value, alteredRows: alteredRows.value }
124+
125+
const targetIndex = positionObjectMode ? newPosition : newPosition - 1
126+
const reordered = [...data.value]
127+
128+
const [movedItem] = reordered.splice(currentIndex, 1)
129+
reordered.splice(Math.max(0, Math.min(targetIndex, reordered.length)), 0, movedItem)
130+
131+
reordered.forEach((r, index) => {
132+
setPositionValue(r, positionObjectMode ? index : index + 1)
133+
markAltered(r)
134+
})
135+
136+
data.value = reordered
137+
return { reorderedData: reordered, alteredRows: alteredRows.value }
138+
}
139+
140+
function changePositionWithinGroup(row, newPosition) {
141+
const rowGroup = getGroupValue(row)
142+
143+
const groups = new Map()
144+
data.value.forEach((r) => {
145+
const g = getGroupValue(r)
146+
if (!groups.has(g)) groups.set(g, [])
147+
groups.get(g).push(r)
148+
})
149+
150+
const groupItems = groups.get(rowGroup)
151+
if (!groupItems) return { reorderedData: data.value, alteredRows: alteredRows.value, exceeded: false }
152+
153+
const originalIndex = groupItems.findIndex((r) => r[dataKey] === row[dataKey])
154+
if (originalIndex === -1) return { reorderedData: data.value, alteredRows: alteredRows.value, exceeded: false }
155+
156+
const maxPosition = groupItems.length - 1
157+
let targetPosition = newPosition
158+
let exceeded = false
159+
160+
if (targetPosition > maxPosition) {
161+
targetPosition = maxPosition
162+
exceeded = true
163+
}
164+
165+
if (targetPosition < 0) targetPosition = 0
166+
if (originalIndex === targetPosition) {
167+
return { reorderedData: data.value, alteredRows: alteredRows.value, exceeded }
168+
}
169+
170+
const [movedItem] = groupItems.splice(originalIndex, 1)
171+
groupItems.splice(targetPosition, 0, movedItem)
172+
updateGroupPositions(groupItems)
173+
174+
const reassembled = []
175+
const seenGroups = new Set()
176+
data.value.forEach((r) => {
177+
const g = getGroupValue(r)
178+
if (!seenGroups.has(g)) {
179+
seenGroups.add(g)
180+
reassembled.push(...(groups.get(g) || []))
181+
}
182+
})
183+
184+
data.value = reassembled
185+
return { reorderedData: reassembled, alteredRows: alteredRows.value, exceeded }
186+
}
187+
188+
const alteredRows = computed(() => {
189+
if (positionObjectMode) {
190+
return data.value.filter((row) => row[positionField]?.altered)
191+
}
192+
if (!snapshotTaken) return []
193+
return data.value.filter((row) => {
194+
const original = originalPositions.get(row[dataKey])
195+
return original !== undefined && original !== row[positionField]
196+
})
197+
})
198+
199+
const hasChanges = computed(() => alteredRows.value.length > 0)
200+
201+
function discardChanges() {
202+
if (positionObjectMode) {
203+
data.value.forEach((row) => {
204+
if (row[positionField] && row[positionField].altered) {
205+
row[positionField].value = row[positionField].immutableValue
206+
row[positionField].altered = false
207+
}
208+
})
209+
210+
if (restrictReorderAcrossGroups && groupColumn) {
211+
const groups = new Map()
212+
data.value.forEach((r) => {
213+
const g = getGroupValue(r)
214+
if (!groups.has(g)) groups.set(g, [])
215+
groups.get(g).push(r)
216+
})
217+
218+
const reassembled = []
219+
const seenGroups = new Set()
220+
data.value.forEach((r) => {
221+
const g = getGroupValue(r)
222+
if (!seenGroups.has(g)) {
223+
seenGroups.add(g)
224+
const items = groups.get(g) || []
225+
items.sort((a, b) => (a[positionField]?.immutableValue ?? 0) - (b[positionField]?.immutableValue ?? 0))
226+
reassembled.push(...items)
227+
}
228+
})
229+
data.value = reassembled
230+
} else {
231+
data.value = [...data.value].sort(
232+
(a, b) => (a[positionField]?.immutableValue ?? 0) - (b[positionField]?.immutableValue ?? 0)
233+
)
234+
}
235+
} else {
236+
if (!snapshotTaken) return
237+
data.value.forEach((row) => {
238+
const original = originalPositions.get(row[dataKey])
239+
if (original !== undefined) {
240+
row[positionField] = original
241+
}
242+
})
243+
data.value = [...data.value].sort((a, b) => a[positionField] - b[positionField])
244+
originalPositions.clear()
245+
snapshotTaken = false
246+
}
247+
}
248+
249+
function getPositionLimits(row) {
250+
if (positionObjectMode && row[positionField]) {
251+
return {
252+
min: row[positionField].min ?? 0,
253+
max: row[positionField].max ?? data.value.length - 1
254+
}
255+
}
256+
257+
if (restrictReorderAcrossGroups && groupColumn) {
258+
const rowGroup = getGroupValue(row)
259+
const groupItems = data.value.filter((r) => getGroupValue(r) === rowGroup)
260+
const groupStart = data.value.indexOf(groupItems[0])
261+
return {
262+
min: groupStart + 1,
263+
max: groupStart + groupItems.length
264+
}
265+
}
266+
return { min: 1, max: data.value.length }
267+
}
268+
269+
function scrollToPosition(position, containerSelector = '.p-datatable') {
270+
setTimeout(() => {
271+
const table = document.querySelector(containerSelector)
272+
const rowElement = table?.querySelector(`#row-${position}`)
273+
if (rowElement) {
274+
rowElement.scrollIntoView({ behavior: 'smooth', block: 'center' })
275+
}
276+
}, 150)
277+
}
278+
279+
return {
280+
onRowReorder,
281+
changePosition,
282+
changePositionWithinGroup,
283+
alteredRows,
284+
hasChanges,
285+
discardChanges,
286+
getPositionLimits,
287+
getGroupValue,
288+
scrollToPosition
289+
}
290+
}

0 commit comments

Comments
 (0)