Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[atable] use pinia to track table state #213

Merged
merged 4 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading