@bagelink/vue 0.0.1218 → 0.0.1224

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