Skip to content

Commit

Permalink
Merge pull request #213 from agritheory/feat-atable-state
Browse files Browse the repository at this point in the history
* feat: use pinia to track table state

* feat: add emit for cell updates

* fix: add changelogs

* test: add pinia to tests

---------

Co-authored-by: Rohan Bansal <[email protected]>
  • Loading branch information
Alchez and Rohan Bansal authored Dec 11, 2024
2 parents 951692d + fcb714c commit 0a990b3
Show file tree
Hide file tree
Showing 37 changed files with 639 additions and 498 deletions.
2 changes: 1 addition & 1 deletion aform/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"@stonecrop/themes": "workspace:*",
"@stonecrop/utilities": "workspace:*",
"@vueuse/core": "^11.1.0",
"vue": "^3.5.6"
"vue": "^3.5.11"
},
"devDependencies": {
"@microsoft/api-documenter": "^7.25.3",
Expand Down
3 changes: 2 additions & 1 deletion atable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"@stonecrop/utilities": "workspace:*",
"@vueuse/components": "^10.11.0",
"@vueuse/core": "^11.1.0",
"vue": "^3.5.6"
"pinia": "^2.3.0",
"vue": "^3.5.11"
},
"devDependencies": {
"@microsoft/api-documenter": "^7.25.3",
Expand Down
95 changes: 32 additions & 63 deletions atable/src/components/ACell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
@blur="updateCellData"
@input="updateCellData"
@click="showModal"
@mousedown="showModal"
class="atable-cell"
:class="pinned ? 'sticky-column' : ''">
:class="pinned ? 'sticky-column' : ''"
v-on-click-outside="store.closeModal">
<component
v-if="column.cellComponent"
:is="column.cellComponent"
Expand All @@ -28,47 +28,46 @@

<script setup lang="ts">
import { KeypressHandlers, defaultKeypressHandlers, useKeyboardNav } from '@stonecrop/utilities'
import { vOnClickOutside } from '@vueuse/components'
import { useElementBounding } from '@vueuse/core'
import { computed, CSSProperties, inject, ref, useTemplateRef } from 'vue'
import { computed, CSSProperties, ref, useTemplateRef } from 'vue'
import TableDataStore from '.'
import type { CellContext } from '@/types'
import { createTableStore } from '@/stores/table'
import { isHtmlString } from '@/utils'
const {
colIndex,
rowIndex,
tableid,
store,
addNavigation = true,
tabIndex = 0,
} = defineProps<{
colIndex: number
rowIndex: number
tableid: string
store: ReturnType<typeof createTableStore>
addNavigation?: boolean | KeypressHandlers
tabIndex?: number
pinned?: boolean
}>()
const tableData = inject<TableDataStore>(tableid)
const cellRef = useTemplateRef<HTMLTableCellElement>('cell')
const { bottom, left } = useElementBounding(cellRef)
// keep a shallow copy of the original cell value for comparison
const originalData = tableData.cellData<any>(colIndex, rowIndex)
const originalData = store.getCellData(colIndex, rowIndex)
const displayValue = store.getCellDisplayValue(colIndex, rowIndex)
const currentData = ref('')
const cellModified = ref(false)
const table = tableData.table
const column = tableData.columns[colIndex]
const row = tableData.rows[rowIndex]
const column = store.columns[colIndex]
const row = store.rows[rowIndex]
const textAlign = column.align || 'center'
const cellWidth = column.width || '40ch'
const isHtmlValue = computed(() => {
// TODO: check if display value is a native DOM element
return typeof displayValue.value === 'string' ? isHtmlString(displayValue.value) : false
return typeof displayValue === 'string' ? isHtmlString(displayValue) : false
})
const cellStyle = computed((): CSSProperties => {
Expand All @@ -77,56 +76,34 @@ const cellStyle = computed((): CSSProperties => {
width: cellWidth,
backgroundColor: !cellModified.value ? 'inherit' : 'var(--sc-cell-modified)',
fontWeight: !cellModified.value ? 'inherit' : 'bold',
paddingLeft: getIndent(colIndex, tableData.display[rowIndex]?.indent),
paddingLeft: store.getIndent(colIndex, store.display[rowIndex]?.indent),
}
})
const displayValue = computed(() => {
const cellData = tableData.cellData<any>(colIndex, rowIndex)
return getFormattedValue(cellData)
})
const getFormattedValue = (value: any) => {
const format = column.format
if (!format) {
return value
}
if (typeof format === 'function') {
return format(value, { table, row, column })
} else if (typeof format === 'string') {
// parse format function from string
// eslint-disable-next-line @typescript-eslint/no-implied-eval
const formatFn: (value: any, context?: CellContext) => string = Function(`"use strict";return (${format})`)()
return formatFn(value, { table, row, column })
}
return value
}
const showModal = () => {
if (column.mask) {
// TODO: add masking to cell values
// column.mask(event)
}
if (column.modalComponent) {
tableData.modal.visible = true
tableData.modal.colIndex = colIndex
tableData.modal.rowIndex = rowIndex
tableData.modal.parent = cellRef.value
tableData.modal.top = bottom.value
tableData.modal.left = left.value
tableData.modal.width = cellWidth
if (typeof column.modalComponent === 'function') {
tableData.modal.component = column.modalComponent({ table, row, column })
} else {
tableData.modal.component = column.modalComponent
}
store.$patch(state => {
state.modal.visible = true
state.modal.colIndex = colIndex
state.modal.rowIndex = rowIndex
state.modal.parent = cellRef.value
state.modal.top = bottom.value
state.modal.left = left.value
state.modal.width = cellWidth
if (typeof column.modalComponent === 'function') {
state.modal.component = column.modalComponent({ table: state.table, row, column })
} else {
state.modal.component = column.modalComponent
}
tableData.modal.componentProps = column.modalComponentExtraProps
state.modal.componentProps = column.modalComponentExtraProps
})
}
}
Expand Down Expand Up @@ -161,7 +138,7 @@ if (addNavigation) {
// if (event) {
// // custom components need to handle their own updateData, this is the default
// if (!column.component) {
// tableData.setCellData(rowIndex, colIndex, cell.value.innerHTML)
// store.setCellData(colIndex, rowIndex, cell.value.innerHTML)
// }
// cellModified.value = true
// }
Expand All @@ -177,7 +154,7 @@ 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 !== getFormattedValue(originalData)
cellModified.value = cellRef.value.textContent !== store.getFormattedValue(colIndex, rowIndex, originalData)
} else {
cellModified.value = cellRef.value.textContent !== originalData
}
Expand All @@ -187,19 +164,11 @@ const updateCellData = () => {
cellRef.value.dispatchEvent(new Event('change'))
if (!column.format) {
// TODO: need to setup reverse format function
tableData.setCellData(rowIndex, colIndex, currentData.value)
store.setCellData(colIndex, rowIndex, currentData.value)
}
}
}
}
const getIndent = (colIndex: number, indentLevel?: number) => {
if (indentLevel && colIndex === 0 && indentLevel > 0) {
return `${indentLevel}ch`
} else {
return 'inherit'
}
}
</script>

<style>
Expand Down
19 changes: 9 additions & 10 deletions atable/src/components/AExpansionRow.vue
Original file line number Diff line number Diff line change
@@ -1,49 +1,48 @@
<template>
<tr v-bind="$attrs" ref="rowEl" :tabindex="tabIndex" class="expandable-row">
<td :tabIndex="-1" @click="tableData.toggleRowExpand(rowIndex)" class="row-index">
<td :tabIndex="-1" @click="store.toggleRowExpand(rowIndex)" class="row-index">
{{ rowExpandSymbol }}
</td>
<slot name="row" />
</tr>
<tr v-if="tableData.display[rowIndex].expanded" ref="rowExpanded" :tabindex="tabIndex" class="expanded-row">
<td :tabIndex="-1" :colspan="tableData.columns.length + 1" class="expanded-row-content">
<tr v-if="store.display[rowIndex].expanded" ref="rowExpanded" :tabindex="tabIndex" class="expanded-row">
<td :tabIndex="-1" :colspan="store.columns.length + 1" class="expanded-row-content">
<slot name="content" />
</td>
</tr>
</template>

<script setup lang="ts">
import { type KeypressHandlers, useKeyboardNav } from '@stonecrop/utilities'
import { computed, inject, useTemplateRef } from 'vue'
import { computed, useTemplateRef } from 'vue'
import TableDataStore from '.'
import { createTableStore } from '@/stores/table'
const {
rowIndex,
tableid,
store,
tabIndex = -1,
addNavigation,
} = defineProps<{
rowIndex: number
tableid: string
store: ReturnType<typeof createTableStore>
tabIndex?: number
addNavigation?: boolean | KeypressHandlers
}>()
const tableData = inject<TableDataStore>(tableid)
const rowRef = useTemplateRef<HTMLTableRowElement>('rowEl')
// const expandedRowRef = useTemplateRef<HTMLDivElement>('rowExpanded')
const rowExpandSymbol = computed(() => {
return tableData.display[rowIndex].expanded ? '' : ''
return store.display[rowIndex].expanded ? '' : ''
})
if (addNavigation) {
const handlers: KeypressHandlers = {
'keydown.control.g': (event: KeyboardEvent) => {
event.stopPropagation()
event.preventDefault()
tableData.toggleRowExpand(rowIndex)
store.toggleRowExpand(rowIndex)
},
}
Expand Down
47 changes: 14 additions & 33 deletions atable/src/components/ARow.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<template>
<tr ref="rowEl" :tabindex="tabIndex" v-show="isRowVisible" class="table-row">
<!-- render numbered/tree view index; skip render for uncounted lists -->
<slot name="index" v-if="tableData.config.view !== 'uncounted'">
<slot name="index" v-if="store.config.view !== 'uncounted'">
<td
v-if="tableData.config.view === 'list'"
v-if="store.config.view === 'list'"
:tabIndex="-1"
class="list-index"
:class="hasPinnedColumns ? 'sticky-index' : ''">
:class="store.hasPinnedColumns ? 'sticky-index' : ''">
{{ rowIndex + 1 }}
</td>
<td
v-else-if="tableData.config.view === 'tree'"
v-else-if="store.config.view === 'tree'"
:tabIndex="-1"
class="tree-index"
:class="hasPinnedColumns ? 'sticky-index' : ''"
@click="toggleRowExpand(rowIndex)">
:class="store.hasPinnedColumns ? 'sticky-index' : ''"
@click="store.toggleRowExpand(rowIndex)">
{{ rowExpandSymbol }}
</td>
</slot>
Expand All @@ -26,46 +26,25 @@

<script setup lang="ts">
import { type KeypressHandlers, useKeyboardNav, defaultKeypressHandlers } from '@stonecrop/utilities'
import { computed, inject, useTemplateRef } from 'vue'
import { useTemplateRef } from 'vue'
import TableDataStore from '.'
import { createTableStore } from '@/stores/table'
const {
rowIndex,
tableid,
store,
tabIndex = -1,
addNavigation = false, // default to allowing cell navigation
} = defineProps<{
rowIndex: number
tableid: string
store: ReturnType<typeof createTableStore>
tabIndex?: number
addNavigation?: boolean | KeypressHandlers
}>()
const tableData = inject<TableDataStore>(tableid)
const rowRef = useTemplateRef<HTMLTableRowElement>('rowEl')
const hasPinnedColumns = computed(() => tableData.columns.some(col => col.pinned))
const isRowVisible = computed(() => {
return tableData.config.view !== 'tree' || tableData.display[rowIndex].isRoot || tableData.display[rowIndex].open
})
const rowExpandSymbol = computed(() => {
if (tableData.config.view !== 'tree') {
return ''
}
if (tableData.display[rowIndex].isRoot || tableData.display[rowIndex].isParent) {
return tableData.display[rowIndex].childrenOpen ? '-' : '+'
}
return ''
})
const toggleRowExpand = (rowIndex: number) => {
tableData.toggleRowExpand(rowIndex)
}
const isRowVisible = store.isRowVisible(rowIndex)
const rowExpandSymbol = store.getRowExpandSymbol(rowIndex)
if (addNavigation) {
let handlers = defaultKeypressHandlers
Expand Down Expand Up @@ -94,6 +73,7 @@ if (addNavigation) {
display: flex;
background-color: white;
}
.list-index {
color: var(--sc-header-text-color);
font-weight: bold;
Expand All @@ -108,6 +88,7 @@ if (addNavigation) {
padding-top: var(--sc-atable-row-padding);
padding-bottom: var(--sc-atable-row-padding);
}
.tree-index {
color: var(--sc-header-text-color);
font-weight: bold;
Expand Down
Loading

0 comments on commit 0a990b3

Please sign in to comment.