Skip to content

Commit

Permalink
[atable] emit cell update in columns with format (#225)
Browse files Browse the repository at this point in the history
* fix: emit cell update in columns with format

* fix: add changelogs

---------

Co-authored-by: Rohan Bansal <[email protected]>
  • Loading branch information
Alchez and Rohan Bansal authored Dec 18, 2024
1 parent f0e33f6 commit fac2994
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 156 deletions.
46 changes: 20 additions & 26 deletions atable/src/components/ACell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
:style="cellStyle"
@focus="onFocus"
@paste="updateCellData"
@blur="updateCellData"
@input="updateCellData"
@click="showModal"
class="atable-cell"
Expand All @@ -28,7 +27,7 @@
<script setup lang="ts">
import { KeypressHandlers, defaultKeypressHandlers, useKeyboardNav } from '@stonecrop/utilities'
import { useElementBounding } from '@vueuse/core'
import { computed, CSSProperties, ref, useTemplateRef, watch } from 'vue'
import { computed, type CSSProperties, ref, useTemplateRef } from 'vue'
import { createTableStore } from '../stores/table'
import { isHtmlString } from '../utils'
Expand All @@ -48,12 +47,13 @@ const {
pinned?: boolean
}>()
const emit = defineEmits<{ cellInput: [colIndex: number, rowIndex: number, newValue: string, oldValue: string] }>()
const cellRef = useTemplateRef<HTMLTableCellElement>('cell')
const { width, height } = useElementBounding(cellRef)
// keep a shallow copy of the original cell value for comparison
const originalData = store.getCellData(colIndex, rowIndex)
const displayValue = ref(store.getCellDisplayValue(colIndex, rowIndex))
const currentData = ref('')
const cellModified = ref(false)
Expand All @@ -63,13 +63,7 @@ const row = store.rows[rowIndex]
const textAlign = column.align || 'center'
const cellWidth = column.width || '40ch'
watch(
() => store.getCellData(colIndex, rowIndex),
cellData => {
displayValue.value = store.getFormattedValue(colIndex, rowIndex, cellData)
}
)
const displayValue = computed(() => store.getCellDisplayValue(colIndex, rowIndex))
const isHtmlValue = computed(() => {
// TODO: check if display value is a native DOM element
return typeof displayValue.value === 'string' ? isHtmlString(displayValue.value) : false
Expand Down Expand Up @@ -156,23 +150,23 @@ const onFocus = () => {
}
}
const updateCellData = () => {
if (cellRef.value) {
// only apply changes if the cell value has changed after being mounted
if (column.format) {
cellModified.value = cellRef.value.textContent !== store.getFormattedValue(colIndex, rowIndex, originalData)
} else {
cellModified.value = cellRef.value.textContent !== originalData
}
const updateCellData = (payload: Event) => {
const target = payload.target as HTMLTableCellElement
if (target.textContent === currentData.value) {
return
}
if (cellRef.value.textContent !== currentData.value) {
currentData.value = cellRef.value.textContent
cellRef.value.dispatchEvent(new Event('change'))
if (!column.format) {
// TODO: need to setup reverse format function
store.setCellData(colIndex, rowIndex, currentData.value)
}
}
emit('cellInput', colIndex, rowIndex, target.textContent, currentData.value)
currentData.value = target.textContent
// only apply changes if the cell value has changed after being mounted
if (column.format) {
cellModified.value = target.textContent !== store.getFormattedValue(colIndex, rowIndex, originalData)
// TODO: need to setup reverse format function?
store.setCellText(colIndex, rowIndex, target.textContent)
} else {
cellModified.value = target.textContent !== originalData
store.setCellData(colIndex, rowIndex, target.textContent)
}
}
</script>
Expand Down
62 changes: 11 additions & 51 deletions atable/src/components/ATable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
textAlign: col?.align || 'center',
minWidth: col?.width || '40ch',
width: store.config.fullWidth ? 'auto' : null,
}" />
}"
@cellInput="emitInput" />
</ARow>
</slot>
</tbody>
Expand Down Expand Up @@ -87,11 +88,14 @@ const tableRef = useTemplateRef<HTMLTableElement>('table')
const rowsValue = modelValue ? modelValue : rows
const store = createTableStore({ columns, rows: rowsValue, id, config })
store.$onAction(({ name, store, args }) => {
store.$onAction(({ name, store, args, after }) => {
if (name === 'setCellData') {
const [colIndex, rowIndex, newCellValue] = args
const prevCellValue = store.getCellData(colIndex, rowIndex)
emit('cellUpdate', [colIndex, rowIndex, newCellValue, prevCellValue])
after(() => {
emit('cellUpdate', colIndex, rowIndex, newCellValue, prevCellValue)
})
}
})
Expand All @@ -114,6 +118,10 @@ onMounted(() => {
}
})
const emitInput = (colIndex: number, rowIndex: number, newCellValue: any, prevCellValue: any) => {
emit('cellUpdate', colIndex, rowIndex, newCellValue, prevCellValue)
}
const assignStickyCellWidths = () => {
const table = tableRef.value
Expand Down Expand Up @@ -148,54 +156,6 @@ const assignStickyCellWidths = () => {
}
}
// const formatCell = (event?: KeyboardEvent, column?: TableColumn, cellData?: any) => {
// let colIndex: number
// const target = event?.target as HTMLTableCellElement
// if (event) {
// colIndex = target.cellIndex
// } else if (column && cellData) {
// colIndex = store.columns.indexOf(column)
// }
// if (!column && 'format' in store.columns[colIndex]) {
// // TODO: (utils) create helper to extract format from string
// const format = store.columns[colIndex].format
// if (typeof format === 'function') {
// return format(target.innerHTML)
// } else if (typeof format === 'string') {
// // parse format function from string
// // eslint-disable-next-line @typescript-eslint/no-implied-eval
// const formatFn: (args: any) => any = Function(`"use strict";return (${format})`)()
// return formatFn(target.innerHTML)
// } else {
// return target.innerHTML
// }
// } else if (cellData && 'format' in column) {
// const format = column.format
// if (typeof format === 'function') {
// return format(cellData)
// } else if (typeof format === 'string') {
// // parse format function from string
// // eslint-disable-next-line @typescript-eslint/no-implied-eval
// const formatFn: (args: any) => any = Function(`"use strict";return (${format})`)()
// return formatFn(cellData)
// } else {
// return cellData
// }
// } else if (cellData && column.type.toLowerCase() in ['int', 'decimal', 'float', 'number', 'percent']) {
// return cellData
// // TODO: number formatting
// } else {
// return cellData
// }
// }
// const moveCursorToEnd = (target: HTMLElement) => {
// target.focus()
// document.execCommand('selectAll', false, null)
// document.getSelection().collapseToEnd()
// }
window.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'Escape') {
if (store.modal.visible) {
Expand Down
10 changes: 9 additions & 1 deletion atable/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ import ATable from './components/ATable.vue'
import ATableHeader from './components/ATableHeader.vue'
import ATableModal from './components/ATableModal.vue'
export { createTableStore } from './stores/table'
export type { CellContext, TableColumn, TableConfig, TableDisplay, TableRow, TableModal } from './types'
export type {
CellContext,
TableColumn,
TableConfig,
TableDisplay,
TableModal,
TableModalProps,
TableRow,
} from './types'

/**
* Install all ATable components
Expand Down
16 changes: 14 additions & 2 deletions atable/src/stores/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const createTableStore = (initData: {
const table = ref(initData.table || createTableObject())
const display = ref(createDisplayObject(initData.display))
const modal = ref(initData.modal || { visible: false })
const updates = ref<Record<string, string>>({})

// getters
const hasPinnedColumns = computed(() => columns.value.some(col => col.pinned))
Expand All @@ -100,6 +101,15 @@ export const createTableStore = (initData: {
rows.value[rowIndex][col.name] = value
}

const setCellText = (colIndex: number, rowIndex: number, value: string) => {
const index = `${colIndex}:${rowIndex}`

if (table.value[index] !== value) {
display.value[rowIndex].rowModified = true
updates.value[index] = value
}
}

const getHeaderCellStyle = (column: TableColumn): CSSProperties => ({
minWidth: column.width || '40ch',
textAlign: column.align || 'center',
Expand Down Expand Up @@ -185,11 +195,12 @@ export const createTableStore = (initData: {
return {
// state
columns,
rows,
config,
table,
display,
modal,
rows,
table,
updates,

// getters
hasPinnedColumns,
Expand All @@ -206,6 +217,7 @@ export const createTableStore = (initData: {
getRowExpandSymbol,
isRowVisible,
setCellData,
setCellText,
toggleRowExpand,
}
})
Expand Down
13 changes: 13 additions & 0 deletions atable/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { createTableStore } from '../stores/table'

/**
* Table column definition.
* @public
Expand Down Expand Up @@ -104,3 +106,14 @@ export type TableModal = {
component?: string
componentProps?: Record<string, any>
}

/**
* Table modal props definition.
* @public
*/
export type TableModalProps = {
[key: string]: any
colIndex: number
rowIndex: number
store: ReturnType<typeof createTableStore>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@stonecrop/atable",
"comment": "emit cell update in columns with format",
"type": "patch"
}
],
"packageName": "@stonecrop/atable"
}
Loading

0 comments on commit fac2994

Please sign in to comment.