@bagelink/vue 0.0.1220 → 0.0.1227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BglComponent.vue.d.ts.map +1 -1
- package/dist/components/Btn.vue.d.ts.map +1 -1
- package/dist/components/Calendar/Index.vue.d.ts +8 -4
- package/dist/components/Calendar/Index.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/header/Header.vue.d.ts +2 -0
- package/dist/components/Calendar/components/header/Header.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/month/AgendaEventTile.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/month/AgendaEvents.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/month/Day.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/month/Event.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/month/Month.vue.d.ts +2 -2
- package/dist/components/Calendar/components/month/Month.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/month/WeekDay.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/partials/EventFlyout.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/week/Day.vue.d.ts.map +1 -1
- package/dist/components/Calendar/components/week/Week.vue.d.ts +2 -2
- package/dist/components/Calendar/components/week/WeekTimeline.vue.d.ts.map +1 -1
- package/dist/components/Calendar/constants.d.ts.map +1 -1
- package/dist/components/Calendar/language/index.d.ts +2 -1
- package/dist/components/Calendar/language/index.d.ts.map +1 -1
- package/dist/components/Calendar/language/keys.d.ts +66 -63
- package/dist/components/Calendar/language/keys.d.ts.map +1 -1
- package/dist/components/Spreadsheet/Index.vue.d.ts +25 -0
- package/dist/components/Spreadsheet/Index.vue.d.ts.map +1 -0
- package/dist/components/form/BagelForm.vue.d.ts +2 -1
- package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePick.vue.d.ts +1 -0
- package/dist/components/form/inputs/DatePick.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/NumberInput.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/components/lightbox/lightbox.types.d.ts +2 -1
- package/dist/components/lightbox/lightbox.types.d.ts.map +1 -1
- package/dist/composables/useSchemaField.d.ts +9 -5
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/index.cjs +1879 -1161
- package/dist/index.mjs +1880 -1162
- package/dist/style.css +733 -763
- package/dist/types/BagelForm.d.ts +2 -1
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Btn.vue +3 -0
- package/src/components/Calendar/Index.vue +13 -16
- package/src/components/Calendar/components/header/Header.vue +17 -139
- package/src/components/Calendar/components/month/AgendaEventTile.vue +1 -10
- package/src/components/Calendar/components/month/AgendaEvents.vue +7 -53
- package/src/components/Calendar/components/month/Day.vue +12 -30
- package/src/components/Calendar/components/month/Event.vue +10 -67
- package/src/components/Calendar/components/month/Month.vue +10 -56
- package/src/components/Calendar/components/month/WeekDay.vue +1 -11
- package/src/components/Calendar/components/partials/EventFlyout.vue +2 -1
- package/src/components/Calendar/components/week/Day.vue +4 -18
- package/src/components/Calendar/components/week/DayEvent.vue +1 -1
- package/src/components/Calendar/components/week/FullDayEvent.vue +2 -2
- package/src/components/Calendar/components/week/Week.vue +1 -1
- package/src/components/Calendar/components/week/WeekTimeline.vue +13 -38
- package/src/components/Calendar/constants.ts +11 -11
- package/src/components/Calendar/language/index.ts +6 -3
- package/src/components/Calendar/language/keys.ts +91 -88
- package/src/components/Calendar/styles/_variables.css +38 -42
- package/src/components/Spreadsheet/Index.vue +867 -0
- package/src/components/form/BagelForm.vue +7 -3
- package/src/components/form/inputs/DatePick.vue +6 -2
- package/src/components/form/inputs/DatePicker.vue +2 -2
- package/src/components/form/inputs/NumberInput.vue +2 -2
- package/src/components/index.ts +1 -0
- package/src/components/lightbox/Lightbox.vue +5 -5
- package/src/components/lightbox/lightbox.types.ts +2 -1
- package/src/composables/useSchemaField.ts +36 -12
- package/src/styles/buttons.css +81 -73
- package/src/styles/layout.css +25 -0
- package/src/styles/mobilLayout.css +25 -0
- package/src/styles/text.css +82 -1
- package/src/styles/theme.css +269 -258
- package/src/types/BagelForm.ts +2 -1
- package/src/utils/BagelFormUtils.ts +2 -1
- package/src/components/Calendar/index.ts +0 -4
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { Btn, Icon, CheckInput, TextInput } from '@bagelink/vue'
|
|
3
|
+
import { computed, ref, watch, nextTick } from 'vue'
|
|
4
|
+
|
|
5
|
+
// Define column configuration types
|
|
6
|
+
type ColumnFormat = 'text' | 'number' | 'currency' | 'date' | 'percentage' | 'image' | 'boolean'
|
|
7
|
+
|
|
8
|
+
interface ColumnConfig {
|
|
9
|
+
key: string
|
|
10
|
+
label?: string
|
|
11
|
+
locked?: boolean
|
|
12
|
+
format?: ColumnFormat
|
|
13
|
+
sortable?: boolean
|
|
14
|
+
width?: string
|
|
15
|
+
fixed?: boolean
|
|
16
|
+
hidden?: boolean
|
|
17
|
+
defaultValue?: any
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Define props interface with column configuration
|
|
21
|
+
interface Props {
|
|
22
|
+
modelValue: Array<Record<string, any>>
|
|
23
|
+
columnConfig?: ColumnConfig[]
|
|
24
|
+
label?: string
|
|
25
|
+
allowAddRow?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { modelValue, columnConfig, label, allowAddRow = true } = defineProps<Props>()
|
|
29
|
+
const emit = defineEmits(['update:modelValue'])
|
|
30
|
+
|
|
31
|
+
// Helper function to flatten an object with dot notation
|
|
32
|
+
function flattenObject(obj: Record<string, any>, prefix = ''): Record<string, any> {
|
|
33
|
+
const flattened: Record<string, any> = {}
|
|
34
|
+
|
|
35
|
+
for (const key in obj) {
|
|
36
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
37
|
+
const value = obj[key]
|
|
38
|
+
const newKey = prefix ? `${prefix}.${key}` : key
|
|
39
|
+
|
|
40
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
41
|
+
Object.assign(flattened, flattenObject(value, newKey))
|
|
42
|
+
} else {
|
|
43
|
+
flattened[newKey] = value
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return flattened
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Helper function to unflatten an object from dot notation
|
|
52
|
+
function unflattenObject(obj: Record<string, any>): Record<string, any> {
|
|
53
|
+
const result: Record<string, any> = {}
|
|
54
|
+
|
|
55
|
+
for (const key in obj) {
|
|
56
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
57
|
+
const keys = key.split('.')
|
|
58
|
+
let current = result
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
61
|
+
const k = keys[i]
|
|
62
|
+
if (!current[k]) {
|
|
63
|
+
current[k] = {}
|
|
64
|
+
}
|
|
65
|
+
current = current[k]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
current[keys[keys.length - 1]] = obj[key]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Create local copy of the modelValue with flattened objects
|
|
76
|
+
const localRows = ref<Array<Record<string, any>>>(
|
|
77
|
+
modelValue
|
|
78
|
+
? modelValue.map(row => flattenObject({ ...row }))
|
|
79
|
+
: []
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
// Update the watch handler to handle flattening
|
|
83
|
+
watch(() => modelValue, (newVal) => {
|
|
84
|
+
localRows.value = newVal
|
|
85
|
+
? newVal.map(row => flattenObject({ ...row }))
|
|
86
|
+
: []
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Update the emit to unflatten the data
|
|
90
|
+
function emitUpdate() {
|
|
91
|
+
emit('update:modelValue', localRows.value.map(row => unflattenObject({ ...row })))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Sort state
|
|
95
|
+
const sortColumn = ref<string | null>(null)
|
|
96
|
+
const sortDirection = ref<'asc' | 'desc'>('asc')
|
|
97
|
+
|
|
98
|
+
// Update the columns computed property
|
|
99
|
+
const columns = computed(() => {
|
|
100
|
+
// Get all unique keys from the data
|
|
101
|
+
const dataKeys = localRows.value.length
|
|
102
|
+
? new Set(localRows.value.flatMap(row => Object.keys(row)))
|
|
103
|
+
: new Set<string>()
|
|
104
|
+
|
|
105
|
+
// Create a map of configured columns
|
|
106
|
+
const configMap = new Map(
|
|
107
|
+
(columnConfig || []).map(config => [config.key, config])
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
// Merge configured columns with data keys
|
|
111
|
+
const allKeys = new Set([...dataKeys, ...(columnConfig?.map(c => c.key) || [])])
|
|
112
|
+
|
|
113
|
+
return Array.from(allKeys).map((key) => {
|
|
114
|
+
const configuredColumn = configMap.get(key)
|
|
115
|
+
return {
|
|
116
|
+
key,
|
|
117
|
+
label: configuredColumn?.label ?? key,
|
|
118
|
+
locked: configuredColumn?.locked ?? false,
|
|
119
|
+
sortable: configuredColumn?.sortable ?? true,
|
|
120
|
+
format: configuredColumn?.format ?? 'text',
|
|
121
|
+
width: configuredColumn?.width,
|
|
122
|
+
fixed: configuredColumn?.fixed ?? false,
|
|
123
|
+
hidden: configuredColumn?.hidden ?? false,
|
|
124
|
+
defaultValue: configuredColumn?.defaultValue
|
|
125
|
+
} as ColumnConfig
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Format cell value based on column configuration
|
|
130
|
+
function formatCellValue(value: any, format?: ColumnFormat): string {
|
|
131
|
+
if (value === null || value === undefined) return ''
|
|
132
|
+
|
|
133
|
+
switch (format) {
|
|
134
|
+
case 'image':
|
|
135
|
+
return String(value) // Return raw value for images
|
|
136
|
+
case 'boolean':
|
|
137
|
+
return '' // Return empty string since we're using CheckInput component
|
|
138
|
+
case 'number':
|
|
139
|
+
return Number(value).toLocaleString()
|
|
140
|
+
case 'currency':
|
|
141
|
+
return Number(value).toLocaleString(undefined, {
|
|
142
|
+
style: 'currency',
|
|
143
|
+
currency: 'USD'
|
|
144
|
+
})
|
|
145
|
+
case 'date':
|
|
146
|
+
return value ? new Date(value).toLocaleDateString() : ''
|
|
147
|
+
case 'percentage':
|
|
148
|
+
return Number(value).toLocaleString(undefined, {
|
|
149
|
+
style: 'percent',
|
|
150
|
+
minimumFractionDigits: 2
|
|
151
|
+
})
|
|
152
|
+
default:
|
|
153
|
+
return String(value)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Update the parseValueForFormat function to handle type conversions properly
|
|
158
|
+
function parseValueForFormat(value: string | boolean, format?: ColumnFormat): any {
|
|
159
|
+
switch (format) {
|
|
160
|
+
case 'boolean':
|
|
161
|
+
return typeof value === 'boolean' ? value : value === 'true'
|
|
162
|
+
case 'number':
|
|
163
|
+
case 'percentage':
|
|
164
|
+
case 'currency':
|
|
165
|
+
if (typeof value === 'boolean') return null
|
|
166
|
+
return value === '' ? null : Number(String(value).replace(/[^0-9.-]/g, ''))
|
|
167
|
+
case 'date':
|
|
168
|
+
if (typeof value === 'boolean') return null
|
|
169
|
+
return value === '' ? null : new Date(String(value)).toISOString()
|
|
170
|
+
default:
|
|
171
|
+
return String(value)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check if a cell is editable
|
|
176
|
+
function isCellEditable(columnKey: string): boolean {
|
|
177
|
+
const column = columns.value.find(col => col.key === columnKey)
|
|
178
|
+
return !(column?.locked ?? false)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Sort rows by column
|
|
182
|
+
function sortByColumn(columnKey: string) {
|
|
183
|
+
const column = columns.value.find(col => col.key === columnKey)
|
|
184
|
+
if (!column?.sortable) return
|
|
185
|
+
|
|
186
|
+
if (sortColumn.value === columnKey) {
|
|
187
|
+
sortDirection.value = sortDirection.value === 'desc' ? 'asc' : 'desc'
|
|
188
|
+
} else {
|
|
189
|
+
sortColumn.value = columnKey
|
|
190
|
+
sortDirection.value = 'desc'
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const sorted = [...localRows.value].sort((a, b) => {
|
|
194
|
+
const aVal = a[columnKey]
|
|
195
|
+
const bVal = b[columnKey]
|
|
196
|
+
|
|
197
|
+
if (aVal === bVal) return 0
|
|
198
|
+
if (aVal === null || aVal === undefined) return 1
|
|
199
|
+
if (bVal === null || bVal === undefined) return -1
|
|
200
|
+
|
|
201
|
+
const modifier = sortDirection.value === 'desc' ? 1 : -1
|
|
202
|
+
return aVal < bVal ? -modifier : modifier
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
localRows.value = sorted
|
|
206
|
+
emitUpdate()
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Variables to handle cell selection
|
|
210
|
+
interface CellPosition {
|
|
211
|
+
row: number
|
|
212
|
+
col: number
|
|
213
|
+
}
|
|
214
|
+
const isSelecting = ref(false)
|
|
215
|
+
const selectionStart = ref<CellPosition | null>(null)
|
|
216
|
+
const selectionEnd = ref<CellPosition | null>(null)
|
|
217
|
+
|
|
218
|
+
// Variable to hold copied cell data
|
|
219
|
+
const copiedData = ref<string[][]>([])
|
|
220
|
+
|
|
221
|
+
// Reactive variable to track the currently editing cell
|
|
222
|
+
const editingCell = ref<CellPosition | null>(null)
|
|
223
|
+
|
|
224
|
+
// Update the editInputRef to use a Map
|
|
225
|
+
const editInputRef = ref(new Map<string, HTMLInputElement>())
|
|
226
|
+
|
|
227
|
+
// Update the ref handling functions
|
|
228
|
+
function setInputRef(el: any, key: string) {
|
|
229
|
+
if (el?.$el instanceof HTMLInputElement) {
|
|
230
|
+
editInputRef.value.set(key, el.$el)
|
|
231
|
+
} else if (el instanceof HTMLInputElement) {
|
|
232
|
+
editInputRef.value.set(key, el)
|
|
233
|
+
} else {
|
|
234
|
+
editInputRef.value.delete(key)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Determines if the given cell is within the currently selected range
|
|
239
|
+
function isCellSelected(row: number, col: number): boolean {
|
|
240
|
+
if (!selectionStart.value || !selectionEnd.value) return false
|
|
241
|
+
const startRow = Math.min(selectionStart.value.row, selectionEnd.value.row)
|
|
242
|
+
const endRow = Math.max(selectionStart.value.row, selectionEnd.value.row)
|
|
243
|
+
const startCol = Math.min(selectionStart.value.col, selectionEnd.value.col)
|
|
244
|
+
const endCol = Math.max(selectionStart.value.col, selectionEnd.value.col)
|
|
245
|
+
return row >= startRow && row <= endRow && col >= startCol && col <= endCol
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Mouse event handlers to manage the selection range
|
|
249
|
+
function handleMouseDown(row: number, col: number) {
|
|
250
|
+
selectionStart.value = { row, col }
|
|
251
|
+
selectionEnd.value = { row, col }
|
|
252
|
+
isSelecting.value = true
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function handleMouseOver(row: number, col: number) {
|
|
256
|
+
if (isSelecting.value && selectionStart.value) {
|
|
257
|
+
selectionEnd.value = { row, col }
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function handleMouseUp() {
|
|
262
|
+
isSelecting.value = false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Add types and state for undo/redo functionality
|
|
266
|
+
interface SpreadsheetChange {
|
|
267
|
+
type: 'cell' | 'row' | 'paste'
|
|
268
|
+
data: {
|
|
269
|
+
rows: Array<Record<string, any>>
|
|
270
|
+
selection?: { start: CellPosition, end: CellPosition }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const undoStack = ref<SpreadsheetChange[]>([])
|
|
275
|
+
const redoStack = ref<SpreadsheetChange[]>([])
|
|
276
|
+
|
|
277
|
+
// Function to save state before making changes
|
|
278
|
+
function saveState(type: SpreadsheetChange['type']) {
|
|
279
|
+
undoStack.value.push({
|
|
280
|
+
type,
|
|
281
|
+
data: {
|
|
282
|
+
rows: JSON.parse(JSON.stringify(localRows.value)),
|
|
283
|
+
selection: selectionStart.value && selectionEnd.value
|
|
284
|
+
? {
|
|
285
|
+
start: { ...selectionStart.value },
|
|
286
|
+
end: { ...selectionEnd.value }
|
|
287
|
+
}
|
|
288
|
+
: undefined
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
// Clear redo stack when new changes are made
|
|
292
|
+
redoStack.value = []
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Undo/Redo functions
|
|
296
|
+
function undo() {
|
|
297
|
+
const change = undoStack.value.pop()
|
|
298
|
+
if (change) {
|
|
299
|
+
// Save current state to redo stack
|
|
300
|
+
redoStack.value.push({
|
|
301
|
+
type: change.type,
|
|
302
|
+
data: {
|
|
303
|
+
rows: JSON.parse(JSON.stringify(localRows.value)),
|
|
304
|
+
selection: selectionStart.value && selectionEnd.value
|
|
305
|
+
? {
|
|
306
|
+
start: { ...selectionStart.value },
|
|
307
|
+
end: { ...selectionEnd.value }
|
|
308
|
+
}
|
|
309
|
+
: undefined
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// Restore previous state
|
|
314
|
+
localRows.value = JSON.parse(JSON.stringify(change.data.rows))
|
|
315
|
+
if (change.data.selection) {
|
|
316
|
+
selectionStart.value = { ...change.data.selection.start }
|
|
317
|
+
selectionEnd.value = { ...change.data.selection.end }
|
|
318
|
+
}
|
|
319
|
+
emitUpdate()
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function redo() {
|
|
324
|
+
const change = redoStack.value.pop()
|
|
325
|
+
if (change) {
|
|
326
|
+
// Save current state to undo stack
|
|
327
|
+
undoStack.value.push({
|
|
328
|
+
type: change.type,
|
|
329
|
+
data: {
|
|
330
|
+
rows: JSON.parse(JSON.stringify(localRows.value)),
|
|
331
|
+
selection: selectionStart.value && selectionEnd.value
|
|
332
|
+
? {
|
|
333
|
+
start: { ...selectionStart.value },
|
|
334
|
+
end: { ...selectionEnd.value }
|
|
335
|
+
}
|
|
336
|
+
: undefined
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
// Restore next state
|
|
341
|
+
localRows.value = JSON.parse(JSON.stringify(change.data.rows))
|
|
342
|
+
if (change.data.selection) {
|
|
343
|
+
selectionStart.value = { ...change.data.selection.start }
|
|
344
|
+
selectionEnd.value = { ...change.data.selection.end }
|
|
345
|
+
}
|
|
346
|
+
emitUpdate()
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Update updateCell to use undo stack
|
|
351
|
+
function updateCell(rowIndex: number, key: string, newValue: string | boolean) {
|
|
352
|
+
const column = columns.value.find(col => col.key === key)
|
|
353
|
+
if (column?.locked) return
|
|
354
|
+
|
|
355
|
+
saveState('cell')
|
|
356
|
+
const parsedValue = parseValueForFormat(newValue, column?.format)
|
|
357
|
+
// If the parsed value is null/undefined, use the default value
|
|
358
|
+
localRows.value[rowIndex][key] = parsedValue ?? column?.defaultValue ?? (column?.format === 'boolean' ? false : '')
|
|
359
|
+
emitUpdate()
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Update the fixed and scrollable columns computed properties to filter out hidden columns
|
|
363
|
+
const fixedColumns = computed(() => {
|
|
364
|
+
return columns.value.filter(col => col.fixed && !col.hidden)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
const scrollableColumns = computed(() => {
|
|
368
|
+
return columns.value.filter(col => !col.fixed && !col.hidden)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
// Update createEmptyRow to use defaultValue from column config
|
|
372
|
+
function createEmptyRow(): Record<string, any> {
|
|
373
|
+
const newRow: Record<string, any> = {}
|
|
374
|
+
columns.value.forEach((col) => {
|
|
375
|
+
// Use defaultValue if provided, otherwise use format-specific defaults
|
|
376
|
+
if (col.defaultValue !== undefined) {
|
|
377
|
+
newRow[col.key] = col.defaultValue
|
|
378
|
+
} else {
|
|
379
|
+
newRow[col.key] = col.format === 'boolean' ? false : ''
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
return newRow
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Update addRow to use the createEmptyRow function
|
|
386
|
+
function addRow() {
|
|
387
|
+
if (columns.value.length) {
|
|
388
|
+
saveState('row')
|
|
389
|
+
localRows.value.push(createEmptyRow())
|
|
390
|
+
emitUpdate()
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Copy function using Navigator Clipboard API
|
|
395
|
+
async function copySelection() {
|
|
396
|
+
if (!selectionStart.value || !selectionEnd.value) return
|
|
397
|
+
|
|
398
|
+
const startRow = Math.min(selectionStart.value.row, selectionEnd.value.row)
|
|
399
|
+
const endRow = Math.max(selectionStart.value.row, selectionEnd.value.row)
|
|
400
|
+
const startCol = Math.min(selectionStart.value.col, selectionEnd.value.col)
|
|
401
|
+
const endCol = Math.max(selectionStart.value.col, selectionEnd.value.col)
|
|
402
|
+
|
|
403
|
+
const selectedData = []
|
|
404
|
+
for (let i = startRow; i <= endRow; i++) {
|
|
405
|
+
const rowData = []
|
|
406
|
+
for (let j = startCol; j <= endCol; j++) {
|
|
407
|
+
const columnKey = columns.value[j].key
|
|
408
|
+
const value = localRows.value[i][columnKey]
|
|
409
|
+
rowData.push(formatCellValue(value, columns.value[j].format))
|
|
410
|
+
}
|
|
411
|
+
selectedData.push(rowData)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Convert to TSV format for clipboard
|
|
415
|
+
const tsvContent = selectedData.map(row => row.join('\t')).join('\n')
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
await navigator.clipboard.writeText(tsvContent)
|
|
419
|
+
} catch (err) {
|
|
420
|
+
console.error('Failed to copy to clipboard:', err)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Paste function using Navigator Clipboard API
|
|
425
|
+
async function pasteSelection() {
|
|
426
|
+
if (!selectionStart.value) return
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
const clipboardText = await navigator.clipboard.readText()
|
|
430
|
+
const rows = clipboardText.split('\n').map(row => row.split('\t'))
|
|
431
|
+
|
|
432
|
+
saveState('paste')
|
|
433
|
+
|
|
434
|
+
const startRow = selectionStart.value.row
|
|
435
|
+
const startCol = selectionStart.value.col
|
|
436
|
+
|
|
437
|
+
// Calculate how many new rows we need to add
|
|
438
|
+
const neededRows = startRow + rows.length - localRows.value.length
|
|
439
|
+
if (neededRows > 0) {
|
|
440
|
+
// Add the required number of new rows
|
|
441
|
+
for (let i = 0; i < neededRows; i++) {
|
|
442
|
+
localRows.value.push(createEmptyRow())
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Update the data
|
|
447
|
+
rows.forEach((rowData, rowIndex) => {
|
|
448
|
+
const targetRow = startRow + rowIndex
|
|
449
|
+
rowData.forEach((cellValue, colIndex) => {
|
|
450
|
+
const targetCol = startCol + colIndex
|
|
451
|
+
if (targetCol >= columns.value.length) return
|
|
452
|
+
|
|
453
|
+
const columnKey = columns.value[targetCol].key
|
|
454
|
+
if (!isCellEditable(columnKey)) return
|
|
455
|
+
|
|
456
|
+
const { format } = columns.value[targetCol]
|
|
457
|
+
localRows.value[targetRow][columnKey] = parseValueForFormat(cellValue, format)
|
|
458
|
+
})
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
emitUpdate()
|
|
462
|
+
} catch (err) {
|
|
463
|
+
console.error('Failed to paste from clipboard:', err)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Add a variable to track the original value before editing
|
|
468
|
+
const editingOriginalValue = ref<string | null>(null)
|
|
469
|
+
|
|
470
|
+
// Update the startEditing function to handle focus properly
|
|
471
|
+
function startEditing(row: number, col: number, initialKey?: string) {
|
|
472
|
+
const columnKey = columns.value[col]?.key
|
|
473
|
+
if (!columnKey) return
|
|
474
|
+
|
|
475
|
+
// Only check editability when we're actually going to edit
|
|
476
|
+
if (initialKey !== undefined && !isCellEditable(columnKey)) return
|
|
477
|
+
|
|
478
|
+
editingCell.value = { row, col }
|
|
479
|
+
editingOriginalValue.value = localRows.value[row][columnKey]?.toString() ?? ''
|
|
480
|
+
|
|
481
|
+
if (initialKey !== undefined) {
|
|
482
|
+
updateCell(row, columnKey, initialKey)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Focus the input on the next tick after Vue has updated the DOM
|
|
486
|
+
nextTick(() => {
|
|
487
|
+
const inputKey = `cell-${row}-${col}`
|
|
488
|
+
const input = editInputRef.value.get(inputKey)
|
|
489
|
+
if (input) {
|
|
490
|
+
input.focus()
|
|
491
|
+
// If we have an initial key, set the selection to the end
|
|
492
|
+
if (initialKey !== undefined) {
|
|
493
|
+
input.value = initialKey
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
})
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Update the stopEditing function to handle cancellation
|
|
500
|
+
function stopEditing(cancelled = false) {
|
|
501
|
+
if (cancelled && editingCell.value && editingOriginalValue.value !== null) {
|
|
502
|
+
const { row, col } = editingCell.value
|
|
503
|
+
const columnKey = columns.value[col]?.key
|
|
504
|
+
if (columnKey) {
|
|
505
|
+
localRows.value[row][columnKey] = editingOriginalValue.value
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
editingCell.value = null
|
|
509
|
+
editingOriginalValue.value = null
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Update the handleStopEditingAndBlur function
|
|
513
|
+
function handleStopEditingAndBlur(cancelled = false) {
|
|
514
|
+
nextTick(() => {
|
|
515
|
+
stopEditing(cancelled)
|
|
516
|
+
nextTick(() => {
|
|
517
|
+
// Force focus back to the main spreadsheet container
|
|
518
|
+
const spreadsheet = document.querySelector('.spreadsheet') as HTMLElement
|
|
519
|
+
if (spreadsheet) {
|
|
520
|
+
spreadsheet.focus()
|
|
521
|
+
}
|
|
522
|
+
})
|
|
523
|
+
})
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
window.addEventListener('mouseup', handleMouseUp)
|
|
527
|
+
|
|
528
|
+
// First add a function to handle row selection
|
|
529
|
+
function selectEntireRow(rowIndex: number) {
|
|
530
|
+
selectionStart.value = { row: rowIndex, col: 0 }
|
|
531
|
+
selectionEnd.value = { row: rowIndex, col: columns.value.length - 1 }
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Add back the handleCellKeyDown function
|
|
535
|
+
function handleCellKeyDown(event: KeyboardEvent, row: number, col: number) {
|
|
536
|
+
// If this cell is not already in edit mode
|
|
537
|
+
if (!(editingCell.value && editingCell.value.row === row && editingCell.value.col === col)) {
|
|
538
|
+
// Start editing if a printable character or Enter is pressed
|
|
539
|
+
if ((event.key.length === 1 && !event.ctrlKey && !event.metaKey) || event.key === 'Enter') {
|
|
540
|
+
event.preventDefault()
|
|
541
|
+
startEditing(row, col, event.key.length === 1 ? event.key : undefined)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Update keyboard shortcuts to include undo/redo
|
|
547
|
+
function handleSpreadsheetKeyDown(event: KeyboardEvent) {
|
|
548
|
+
// Don't intercept keyboard shortcuts when editing a cell
|
|
549
|
+
if (editingCell.value) return
|
|
550
|
+
|
|
551
|
+
const isCtrlOrCmd = event.ctrlKey || event.metaKey
|
|
552
|
+
|
|
553
|
+
if (isCtrlOrCmd) {
|
|
554
|
+
switch (event.key.toLowerCase()) {
|
|
555
|
+
case 'c':
|
|
556
|
+
event.preventDefault()
|
|
557
|
+
copySelection()
|
|
558
|
+
break
|
|
559
|
+
case 'v':
|
|
560
|
+
event.preventDefault()
|
|
561
|
+
pasteSelection()
|
|
562
|
+
break
|
|
563
|
+
case 'z':
|
|
564
|
+
event.preventDefault()
|
|
565
|
+
if (event.shiftKey) {
|
|
566
|
+
redo()
|
|
567
|
+
} else {
|
|
568
|
+
undo()
|
|
569
|
+
}
|
|
570
|
+
break
|
|
571
|
+
case 'y':
|
|
572
|
+
event.preventDefault()
|
|
573
|
+
redo()
|
|
574
|
+
break
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Add computed properties for undo/redo stack state
|
|
580
|
+
const canUndo = computed(() => undoStack.value.length > 0)
|
|
581
|
+
const canRedo = computed(() => redoStack.value.length > 0)
|
|
582
|
+
|
|
583
|
+
// Add after other ref declarations
|
|
584
|
+
const search = ref('')
|
|
585
|
+
|
|
586
|
+
// Add the filteredRows computed property after the columns computed
|
|
587
|
+
const filteredRows = computed(() => {
|
|
588
|
+
if (!search.value) return localRows.value
|
|
589
|
+
|
|
590
|
+
const searchTerm = search.value.toLowerCase()
|
|
591
|
+
return localRows.value.filter((row) => {
|
|
592
|
+
// Check all values in the row, including hidden columns
|
|
593
|
+
return Object.values(row).some((value) => {
|
|
594
|
+
if (value === null || value === undefined) return false
|
|
595
|
+
return String(value).toLowerCase().includes(searchTerm)
|
|
596
|
+
})
|
|
597
|
+
})
|
|
598
|
+
})
|
|
599
|
+
</script>
|
|
600
|
+
|
|
601
|
+
<template>
|
|
602
|
+
<div class="w-100p overflow-hidden" tabindex="-1" @keydown="handleSpreadsheetKeyDown">
|
|
603
|
+
<div class="flex gap-05 py-05 justify-content-end m_flex-wrap">
|
|
604
|
+
<label v-if="label" class="label me-auto">{{ label }}</label>
|
|
605
|
+
<div class="flex gap-075">
|
|
606
|
+
<TextInput v-model="search" icon="search" placeholder="Search" class="m-0 max-w200px" />
|
|
607
|
+
<Btn v-tooltip="'Paste'" flat thin round icon="paste" @click="pasteSelection" />
|
|
608
|
+
<Btn v-tooltip="'copy'" flat thin round icon="copy" @click="copySelection" />
|
|
609
|
+
<Btn v-tooltip="'Undo'" flat thin round icon="undo" :disabled="!canUndo" @click="undo" />
|
|
610
|
+
<Btn v-tooltip="'Redo'" flat thin round icon="redo" :disabled="!canRedo" @click="redo" />
|
|
611
|
+
</div>
|
|
612
|
+
</div>
|
|
613
|
+
<div class="spreadsheet" @mouseup="handleMouseUp">
|
|
614
|
+
<div class="flex w-100p relative">
|
|
615
|
+
<!-- Fixed Columns -->
|
|
616
|
+
<table v-if="fixedColumns.length" class="fixed-columns sticky z-2 start-0 bg-white">
|
|
617
|
+
<thead>
|
|
618
|
+
<tr>
|
|
619
|
+
<th class="row-number-header bg-white" />
|
|
620
|
+
<th
|
|
621
|
+
v-for="col in fixedColumns"
|
|
622
|
+
:key="col.key"
|
|
623
|
+
>
|
|
624
|
+
<span @click="col.sortable && sortByColumn(col.key)">
|
|
625
|
+
{{ col.label || col.key }}
|
|
626
|
+
</span>
|
|
627
|
+
<Icon
|
|
628
|
+
v-if="sortColumn === col.key"
|
|
629
|
+
class="line-height-0 transition-400"
|
|
630
|
+
name="keyboard_arrow_down" :class="{ 'rotate-180': sortDirection === 'desc' }"
|
|
631
|
+
/>
|
|
632
|
+
</th>
|
|
633
|
+
</tr>
|
|
634
|
+
</thead>
|
|
635
|
+
<tbody>
|
|
636
|
+
<tr v-for="(row, rowIndex) in filteredRows" :key="rowIndex">
|
|
637
|
+
<td class="row-number txt-center hover user-select-none pointer txt12 regular" @click="selectEntireRow(rowIndex)">
|
|
638
|
+
{{ rowIndex + 1 }}
|
|
639
|
+
</td>
|
|
640
|
+
<td
|
|
641
|
+
v-for="col in fixedColumns"
|
|
642
|
+
:key="col.key"
|
|
643
|
+
:class="{
|
|
644
|
+
selected: isCellSelected(rowIndex, fixedColumns.indexOf(col)),
|
|
645
|
+
locked: !isCellEditable(col.key),
|
|
646
|
+
}"
|
|
647
|
+
:style="{ width: col.width }"
|
|
648
|
+
:tabindex="col.hidden ? undefined : 0"
|
|
649
|
+
@mousedown="handleMouseDown(rowIndex, fixedColumns.indexOf(col))"
|
|
650
|
+
@mouseover="handleMouseOver(rowIndex, fixedColumns.indexOf(col))"
|
|
651
|
+
@focusin="handleMouseOver(rowIndex, fixedColumns.indexOf(col))"
|
|
652
|
+
@dblclick="startEditing(rowIndex, fixedColumns.indexOf(col))"
|
|
653
|
+
@keydown="handleCellKeyDown($event, rowIndex, fixedColumns.indexOf(col))"
|
|
654
|
+
>
|
|
655
|
+
<template v-if="editingCell && editingCell.row === rowIndex && editingCell.col === fixedColumns.indexOf(col)">
|
|
656
|
+
<input
|
|
657
|
+
:ref="el => setInputRef(el, `cell-${rowIndex}-${fixedColumns.indexOf(col)}`)"
|
|
658
|
+
:value="row[col.key]"
|
|
659
|
+
type="text"
|
|
660
|
+
class="spreadsheet-input"
|
|
661
|
+
@input="(e: Event) => updateCell(rowIndex, col.key, (e.target as HTMLInputElement).value)"
|
|
662
|
+
@blur="handleStopEditingAndBlur(false)"
|
|
663
|
+
@keydown.enter.prevent="handleStopEditingAndBlur(false)"
|
|
664
|
+
@keydown.esc.prevent="handleStopEditingAndBlur(true)"
|
|
665
|
+
@mousedown.stop
|
|
666
|
+
>
|
|
667
|
+
<span class="spreadsheet-cell spreadsheetCellPlaceHolder">{{ formatCellValue(row[col.key], col.format) }}</span>
|
|
668
|
+
</template>
|
|
669
|
+
<template v-else>
|
|
670
|
+
<template v-if="col.format === 'image'">
|
|
671
|
+
<div class="h40px w-100p flex align-items-center justify-content-center overflow-hidden">
|
|
672
|
+
<img class=" w-100p h-100p contain radius-05" :src="row[col.key]" :alt="col.label || col.key">
|
|
673
|
+
</div>
|
|
674
|
+
</template>
|
|
675
|
+
<template v-else-if="col.format === 'boolean'">
|
|
676
|
+
<CheckInput
|
|
677
|
+
:modelValue="!!row[col.key]"
|
|
678
|
+
:disabled="!isCellEditable(col.key)"
|
|
679
|
+
@update:modelValue="(value: boolean | any[] | undefined) => updateCell(rowIndex, col.key, !!value)"
|
|
680
|
+
@mousedown.stop
|
|
681
|
+
/>
|
|
682
|
+
</template>
|
|
683
|
+
<template v-else>
|
|
684
|
+
<span class="spreadsheet-cell">{{ formatCellValue(row[col.key], col.format) }}</span>
|
|
685
|
+
</template>
|
|
686
|
+
</template>
|
|
687
|
+
</td>
|
|
688
|
+
</tr>
|
|
689
|
+
</tbody>
|
|
690
|
+
</table>
|
|
691
|
+
|
|
692
|
+
<!-- Scrollable Columns -->
|
|
693
|
+
<div class="flex-shrink flex-grow overflow-x">
|
|
694
|
+
<table class="w-100p">
|
|
695
|
+
<thead>
|
|
696
|
+
<tr>
|
|
697
|
+
<th v-if="!fixedColumns.length" class="row-number-header bg-white" />
|
|
698
|
+
<th
|
|
699
|
+
v-for="col in scrollableColumns"
|
|
700
|
+
:key="col.key"
|
|
701
|
+
>
|
|
702
|
+
<span @click="col.sortable && sortByColumn(col.key)">
|
|
703
|
+
{{ col.label || col.key }}
|
|
704
|
+
</span>
|
|
705
|
+
|
|
706
|
+
<Icon v-if="sortColumn === col.key" name="keyboard_arrow_down" class="line-height-0 inline-block" :class="{ 'rotate-180': sortColumn === col.key && sortDirection === 'desc' }" />
|
|
707
|
+
</th>
|
|
708
|
+
</tr>
|
|
709
|
+
</thead>
|
|
710
|
+
<tbody>
|
|
711
|
+
<tr v-for="(row, rowIndex) in filteredRows" :key="rowIndex">
|
|
712
|
+
<td v-if="!fixedColumns.length" class="row-number txt-center hover user-select-none pointer txt12 regular" @click="selectEntireRow(rowIndex)">
|
|
713
|
+
{{ rowIndex + 1 }}
|
|
714
|
+
</td>
|
|
715
|
+
<td
|
|
716
|
+
v-for="(col, colIndex) in scrollableColumns"
|
|
717
|
+
:key="col.key"
|
|
718
|
+
:class="{
|
|
719
|
+
selected: isCellSelected(rowIndex, fixedColumns.length + colIndex),
|
|
720
|
+
locked: !isCellEditable(col.key),
|
|
721
|
+
}"
|
|
722
|
+
:style="{ width: col.width }"
|
|
723
|
+
:tabindex="col.hidden ? undefined : 0"
|
|
724
|
+
@mousedown="handleMouseDown(rowIndex, fixedColumns.length + colIndex)"
|
|
725
|
+
@mouseover="handleMouseOver(rowIndex, fixedColumns.length + colIndex)"
|
|
726
|
+
@focusin="handleMouseOver(rowIndex, fixedColumns.length + colIndex)"
|
|
727
|
+
@dblclick="startEditing(rowIndex, fixedColumns.length + colIndex)"
|
|
728
|
+
@keydown="handleCellKeyDown($event, rowIndex, fixedColumns.length + colIndex)"
|
|
729
|
+
>
|
|
730
|
+
<template v-if="editingCell && editingCell.row === rowIndex && editingCell.col === (fixedColumns.length + colIndex)">
|
|
731
|
+
<input
|
|
732
|
+
:ref="el => setInputRef(el, `cell-${rowIndex}-${fixedColumns.length + colIndex}`)"
|
|
733
|
+
:value="row[col.key]"
|
|
734
|
+
type="text"
|
|
735
|
+
class="spreadsheet-input"
|
|
736
|
+
@input="(e: Event) => updateCell(rowIndex, col.key, (e.target as HTMLInputElement).value)"
|
|
737
|
+
@blur="handleStopEditingAndBlur(false)"
|
|
738
|
+
@keydown.enter.prevent="handleStopEditingAndBlur(false)"
|
|
739
|
+
@keydown.esc.prevent="handleStopEditingAndBlur(true)"
|
|
740
|
+
@mousedown.stop
|
|
741
|
+
>
|
|
742
|
+
<span class="spreadsheet-cell spreadsheetCellPlaceHolder">{{ formatCellValue(row[col.key], col.format) }}</span>
|
|
743
|
+
</template>
|
|
744
|
+
<template v-else>
|
|
745
|
+
<template v-if="col.format === 'image'">
|
|
746
|
+
<div v-if="row[col.key]" class="h40px w-100p flex align-items-center justify-content-center overflow-hidden">
|
|
747
|
+
<img class=" w-100p h-100p contain radius-05" :src="row[col.key]" :alt="col.label || col.key">
|
|
748
|
+
</div>
|
|
749
|
+
</template>
|
|
750
|
+
<template v-else-if="col.format === 'boolean'">
|
|
751
|
+
<CheckInput
|
|
752
|
+
:modelValue="!!row[col.key]"
|
|
753
|
+
:disabled="!isCellEditable(col.key)"
|
|
754
|
+
@update:modelValue="(value: boolean | any[] | undefined) => updateCell(rowIndex, col.key, !!value)"
|
|
755
|
+
@mousedown.stop
|
|
756
|
+
/>
|
|
757
|
+
</template>
|
|
758
|
+
<template v-else>
|
|
759
|
+
<span class="spreadsheet-cell">{{ formatCellValue(row[col.key], col.format) }}</span>
|
|
760
|
+
</template>
|
|
761
|
+
</template>
|
|
762
|
+
</td>
|
|
763
|
+
</tr>
|
|
764
|
+
</tbody>
|
|
765
|
+
</table>
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
<Btn v-if="allowAddRow" outline thin round icon="add" value="Add Row" class="mt-05" @click="addRow" />
|
|
769
|
+
</div>
|
|
770
|
+
</div>
|
|
771
|
+
</template>
|
|
772
|
+
|
|
773
|
+
<style scoped>
|
|
774
|
+
.fixed-columns {
|
|
775
|
+
border-right: 2px solid var(--border-color);
|
|
776
|
+
}
|
|
777
|
+
.spreadsheet table {
|
|
778
|
+
border-collapse: collapse;
|
|
779
|
+
}
|
|
780
|
+
.spreadsheet th, .spreadsheet td {
|
|
781
|
+
border: 1px solid var(--border-color);
|
|
782
|
+
padding: 0.1rem 0.5rem;
|
|
783
|
+
min-width: 80px;
|
|
784
|
+
background: var(--bgl-white);
|
|
785
|
+
user-select: none;
|
|
786
|
+
}
|
|
787
|
+
.spreadsheet th {
|
|
788
|
+
background: var(--input-bg);
|
|
789
|
+
white-space: nowrap;
|
|
790
|
+
position: relative;
|
|
791
|
+
padding: 0.25rem 0.5rem;
|
|
792
|
+
font-weight: 500;
|
|
793
|
+
text-align: start;
|
|
794
|
+
}
|
|
795
|
+
.spreadsheet th .bgl_icon-font{
|
|
796
|
+
vertical-align: middle;
|
|
797
|
+
}
|
|
798
|
+
.spreadsheet td.selected {
|
|
799
|
+
background: var(--bgl-primary-light);
|
|
800
|
+
}
|
|
801
|
+
.spreadsheet td.locked {
|
|
802
|
+
background: var(--bgl-gray-light);
|
|
803
|
+
cursor: default;
|
|
804
|
+
}
|
|
805
|
+
.spreadsheet td.locked.selected {
|
|
806
|
+
background: var(--bgl-primary-light);
|
|
807
|
+
}
|
|
808
|
+
.spreadsheet td {
|
|
809
|
+
height: 40px;
|
|
810
|
+
vertical-align: middle;
|
|
811
|
+
}
|
|
812
|
+
.spreadsheet td:has(img){
|
|
813
|
+
padding: 0;
|
|
814
|
+
}
|
|
815
|
+
.spreadsheet td span{
|
|
816
|
+
display: block;
|
|
817
|
+
display: -webkit-box;
|
|
818
|
+
max-width: 100%;
|
|
819
|
+
-webkit-box-orient: vertical;
|
|
820
|
+
overflow: hidden;
|
|
821
|
+
text-overflow: ellipsis;
|
|
822
|
+
-webkit-line-clamp: 1;
|
|
823
|
+
word-break: break-all;
|
|
824
|
+
}
|
|
825
|
+
.spreadsheet input {
|
|
826
|
+
width: 100%;
|
|
827
|
+
border: none;
|
|
828
|
+
background: transparent;
|
|
829
|
+
padding: 0;
|
|
830
|
+
margin: 0;
|
|
831
|
+
min-height: 0;
|
|
832
|
+
min-width: 0;
|
|
833
|
+
}
|
|
834
|
+
.spreadsheet input:focus {
|
|
835
|
+
outline: 2px solid var(--bgl-primary);
|
|
836
|
+
outline-offset: 6px;
|
|
837
|
+
}
|
|
838
|
+
.spreadsheet th.sortable {
|
|
839
|
+
cursor: pointer;
|
|
840
|
+
}
|
|
841
|
+
.row-number-header, .row-number {
|
|
842
|
+
width: fit-content;
|
|
843
|
+
min-width: fit-content !important;
|
|
844
|
+
padding: 0.1rem 0.7rem !important;
|
|
845
|
+
}
|
|
846
|
+
.spreadsheet td .bgl-checkbox{
|
|
847
|
+
margin: 0;
|
|
848
|
+
text-align: center;
|
|
849
|
+
justify-content: center;
|
|
850
|
+
|
|
851
|
+
}
|
|
852
|
+
.spreadsheet td:has(.bgl-checkbox){
|
|
853
|
+
text-align: center;
|
|
854
|
+
background: var(--input-bg);
|
|
855
|
+
}
|
|
856
|
+
.spreadsheet td:has(:checked){
|
|
857
|
+
background: var(--bgl-primary-light);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.spreadsheetCellPlaceHolder{
|
|
861
|
+
height: 0px;
|
|
862
|
+
overflow: hidden;
|
|
863
|
+
opactiy: 0;
|
|
864
|
+
poiner-events: none;
|
|
865
|
+
user-select: none;
|
|
866
|
+
}
|
|
867
|
+
</style>
|