@bagelink/vue 1.0.16 → 1.0.18
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/Spreadsheet/Index.vue.d.ts +89 -1
- package/dist/components/Spreadsheet/Index.vue.d.ts.map +1 -1
- package/dist/components/Spreadsheet/SpreadsheetTable.vue.d.ts +37 -0
- package/dist/components/Spreadsheet/SpreadsheetTable.vue.d.ts.map +1 -0
- package/dist/index.cjs +1052 -940
- package/dist/index.mjs +1052 -940
- package/dist/style.css +45 -19
- package/package.json +1 -1
- package/src/components/Spreadsheet/Index.vue +179 -248
- package/src/components/Spreadsheet/SpreadsheetTable.vue +317 -0
package/dist/style.css
CHANGED
|
@@ -4404,20 +4404,20 @@ body:has(.bg-dark.is-active) {
|
|
|
4404
4404
|
background: transparent;
|
|
4405
4405
|
}
|
|
4406
4406
|
|
|
4407
|
-
.fixed-columns[data-v-
|
|
4407
|
+
.fixed-columns[data-v-147c95d0] {
|
|
4408
4408
|
border-right: 2px solid var(--border-color);
|
|
4409
4409
|
}
|
|
4410
|
-
|
|
4410
|
+
table[data-v-147c95d0] {
|
|
4411
4411
|
border-collapse: collapse;
|
|
4412
4412
|
}
|
|
4413
|
-
|
|
4413
|
+
th[data-v-147c95d0], td[data-v-147c95d0] {
|
|
4414
4414
|
border: 1px solid var(--border-color);
|
|
4415
4415
|
padding: 0.1rem 0.5rem;
|
|
4416
4416
|
min-width: 80px;
|
|
4417
4417
|
background: var(--bgl-white);
|
|
4418
4418
|
user-select: none;
|
|
4419
4419
|
}
|
|
4420
|
-
|
|
4420
|
+
th[data-v-147c95d0] {
|
|
4421
4421
|
background: var(--input-bg);
|
|
4422
4422
|
white-space: nowrap;
|
|
4423
4423
|
position: relative;
|
|
@@ -4425,27 +4425,27 @@ body:has(.bg-dark.is-active) {
|
|
|
4425
4425
|
font-weight: 500;
|
|
4426
4426
|
text-align: start;
|
|
4427
4427
|
}
|
|
4428
|
-
|
|
4428
|
+
th .bgl_icon-font[data-v-147c95d0]{
|
|
4429
4429
|
vertical-align: middle;
|
|
4430
4430
|
}
|
|
4431
|
-
|
|
4431
|
+
td.selected[data-v-147c95d0] {
|
|
4432
4432
|
background: var(--bgl-primary-light);
|
|
4433
4433
|
}
|
|
4434
|
-
|
|
4434
|
+
td.locked[data-v-147c95d0] {
|
|
4435
4435
|
background: var(--bgl-gray-light);
|
|
4436
4436
|
cursor: default;
|
|
4437
4437
|
}
|
|
4438
|
-
|
|
4438
|
+
td.locked.selected[data-v-147c95d0] {
|
|
4439
4439
|
background: var(--bgl-primary-light);
|
|
4440
4440
|
}
|
|
4441
|
-
|
|
4441
|
+
td[data-v-147c95d0] {
|
|
4442
4442
|
height: 40px;
|
|
4443
4443
|
vertical-align: middle;
|
|
4444
4444
|
}
|
|
4445
|
-
|
|
4445
|
+
td[data-v-147c95d0]:has(img){
|
|
4446
4446
|
padding: 0;
|
|
4447
4447
|
}
|
|
4448
|
-
|
|
4448
|
+
td span[data-v-147c95d0]{
|
|
4449
4449
|
display: block;
|
|
4450
4450
|
display: -webkit-box;
|
|
4451
4451
|
max-width: 100%;
|
|
@@ -4455,7 +4455,7 @@ body:has(.bg-dark.is-active) {
|
|
|
4455
4455
|
-webkit-line-clamp: 1;
|
|
4456
4456
|
word-break: break-all;
|
|
4457
4457
|
}
|
|
4458
|
-
|
|
4458
|
+
input[data-v-147c95d0] {
|
|
4459
4459
|
width: 100%;
|
|
4460
4460
|
border: none;
|
|
4461
4461
|
background: transparent;
|
|
@@ -4464,37 +4464,63 @@ body:has(.bg-dark.is-active) {
|
|
|
4464
4464
|
min-height: 0;
|
|
4465
4465
|
min-width: 0;
|
|
4466
4466
|
}
|
|
4467
|
-
|
|
4467
|
+
input[data-v-147c95d0]:focus {
|
|
4468
4468
|
outline: 2px solid var(--bgl-primary);
|
|
4469
4469
|
outline-offset: 6px;
|
|
4470
4470
|
}
|
|
4471
|
-
|
|
4471
|
+
th.sortable[data-v-147c95d0] {
|
|
4472
4472
|
cursor: pointer;
|
|
4473
4473
|
}
|
|
4474
|
-
.row-number-header[data-v-
|
|
4474
|
+
.row-number-header[data-v-147c95d0], .row-number[data-v-147c95d0] {
|
|
4475
4475
|
width: fit-content;
|
|
4476
4476
|
min-width: fit-content !important;
|
|
4477
4477
|
padding: 0.1rem 0.7rem !important;
|
|
4478
4478
|
}
|
|
4479
|
-
|
|
4479
|
+
td .bgl-checkbox[data-v-147c95d0]{
|
|
4480
4480
|
margin: 0;
|
|
4481
4481
|
text-align: center;
|
|
4482
4482
|
justify-content: center;
|
|
4483
4483
|
}
|
|
4484
|
-
|
|
4484
|
+
td[data-v-147c95d0]:has(.bgl-checkbox){
|
|
4485
4485
|
text-align: center;
|
|
4486
4486
|
background: var(--input-bg);
|
|
4487
4487
|
}
|
|
4488
|
-
|
|
4488
|
+
td[data-v-147c95d0]:has(:checked){
|
|
4489
4489
|
background: var(--bgl-primary-light);
|
|
4490
4490
|
}
|
|
4491
|
-
.spreadsheetCellPlaceHolder[data-v-
|
|
4491
|
+
.spreadsheetCellPlaceHolder[data-v-147c95d0]{
|
|
4492
4492
|
height: 0px;
|
|
4493
4493
|
overflow: hidden;
|
|
4494
4494
|
opacity: 0;
|
|
4495
4495
|
pointer-events: none;
|
|
4496
4496
|
user-select: none;
|
|
4497
4497
|
}
|
|
4498
|
+
.th-content[data-v-147c95d0] {
|
|
4499
|
+
position: relative;
|
|
4500
|
+
display: flex;
|
|
4501
|
+
align-items: center;
|
|
4502
|
+
width: 100%;
|
|
4503
|
+
height: 100%;
|
|
4504
|
+
}
|
|
4505
|
+
.resize-handle[data-v-147c95d0] {
|
|
4506
|
+
position: absolute;
|
|
4507
|
+
right: -8px;
|
|
4508
|
+
top: 0;
|
|
4509
|
+
bottom: 0;
|
|
4510
|
+
width: 4px;
|
|
4511
|
+
z-index: 100;
|
|
4512
|
+
cursor: col-resize;
|
|
4513
|
+
background: transparent;
|
|
4514
|
+
transition: background 0.2s;
|
|
4515
|
+
}
|
|
4516
|
+
.resize-handle[data-v-147c95d0]:hover {
|
|
4517
|
+
background: var(--bgl-primary);
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4520
|
+
/* Spreadsheet container styles */
|
|
4521
|
+
.spreadsheet[data-v-e4d0dbbe] {
|
|
4522
|
+
user-select: none;
|
|
4523
|
+
}
|
|
4498
4524
|
|
|
4499
4525
|
.zoomer-debug[data-v-379819e1] {
|
|
4500
4526
|
position: fixed;
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { VNode } from 'vue'
|
|
3
|
+
import { Btn, Icon, CheckInput, TextInput, localRef } from '@bagelink/vue'
|
|
4
|
+
import { computed, ref, watch, nextTick, onUnmounted } from 'vue'
|
|
5
|
+
import Dropdown from '../Dropdown.vue'
|
|
6
|
+
import SpreadsheetTable from './SpreadsheetTable.vue'
|
|
4
7
|
|
|
5
8
|
// Define column configuration types
|
|
6
9
|
type ColumnFormat = 'text' | 'number' | 'currency' | 'date' | 'percentage' | 'image' | 'boolean'
|
|
@@ -91,6 +94,9 @@ function emitUpdate() {
|
|
|
91
94
|
emit('update:modelValue', localRows.value.map(row => unflattenObject({ ...row })))
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
// After other ref declarations but before the columns computed property
|
|
98
|
+
const visibleColumns = ref<string[]>([])
|
|
99
|
+
|
|
94
100
|
// Sort state
|
|
95
101
|
const sortColumn = ref<string | null>(null)
|
|
96
102
|
const sortDirection = ref<'asc' | 'desc'>('asc')
|
|
@@ -178,7 +184,7 @@ function isCellEditable(columnKey: string): boolean {
|
|
|
178
184
|
return !(column?.locked ?? false)
|
|
179
185
|
}
|
|
180
186
|
|
|
181
|
-
//
|
|
187
|
+
// Update the sortByColumn function to preserve column visibility
|
|
182
188
|
function sortByColumn(columnKey: string) {
|
|
183
189
|
const column = columns.value.find(col => col.key === columnKey)
|
|
184
190
|
if (!column?.sortable) return
|
|
@@ -202,8 +208,16 @@ function sortByColumn(columnKey: string) {
|
|
|
202
208
|
return aVal < bVal ? -modifier : modifier
|
|
203
209
|
})
|
|
204
210
|
|
|
211
|
+
// Save the current visibleColumns state
|
|
212
|
+
const currentVisibleColumns = [...visibleColumns.value]
|
|
213
|
+
|
|
205
214
|
localRows.value = sorted
|
|
206
215
|
emitUpdate()
|
|
216
|
+
|
|
217
|
+
// Restore the visibleColumns state after the update
|
|
218
|
+
nextTick(() => {
|
|
219
|
+
visibleColumns.value = currentVisibleColumns
|
|
220
|
+
})
|
|
207
221
|
}
|
|
208
222
|
|
|
209
223
|
// Variables to handle cell selection
|
|
@@ -356,13 +370,25 @@ function updateCell(rowIndex: number, key: string, newValue: string | boolean) {
|
|
|
356
370
|
emitUpdate()
|
|
357
371
|
}
|
|
358
372
|
|
|
359
|
-
//
|
|
373
|
+
// After other ref declarations but before computed properties
|
|
374
|
+
// const visibleColumns = ref<string[]>([])
|
|
375
|
+
|
|
376
|
+
// After the columns computed property
|
|
377
|
+
// Initialize visibleColumns with all columns when columns change
|
|
378
|
+
watch(() => columns.value, (newColumns) => {
|
|
379
|
+
if (newColumns.length > 0 && visibleColumns.value.length === 0) {
|
|
380
|
+
visibleColumns.value = newColumns.filter(col => !col.hidden).map(col => col.key)
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
// Update the fixedColumns computed property
|
|
360
385
|
const fixedColumns = computed(() => {
|
|
361
|
-
return columns.value.filter(col => col.fixed && !col.hidden)
|
|
386
|
+
return columns.value.filter(col => col.fixed && !col.hidden && visibleColumns.value.includes(col.key))
|
|
362
387
|
})
|
|
363
388
|
|
|
389
|
+
// Update the scrollableColumns computed property
|
|
364
390
|
const scrollableColumns = computed(() => {
|
|
365
|
-
return columns.value.filter(col => !col.fixed && !col.hidden)
|
|
391
|
+
return columns.value.filter(col => !col.fixed && !col.hidden && visibleColumns.value.includes(col.key))
|
|
366
392
|
})
|
|
367
393
|
|
|
368
394
|
// Update createEmptyRow to use defaultValue from column config
|
|
@@ -418,7 +444,16 @@ async function copySelection() {
|
|
|
418
444
|
}
|
|
419
445
|
}
|
|
420
446
|
|
|
421
|
-
//
|
|
447
|
+
// Add a ref for the search input
|
|
448
|
+
const searchInputRef = ref<any>(null)
|
|
449
|
+
|
|
450
|
+
// Add a method to check if the search input is focused
|
|
451
|
+
function isSearchFocused(): boolean {
|
|
452
|
+
const inputElement = searchInputRef.value?.$el?.querySelector('input')
|
|
453
|
+
return document.activeElement === inputElement
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Update the pasteSelection function to handle search focus
|
|
422
457
|
async function pasteSelection() {
|
|
423
458
|
if (!selectionStart.value) return
|
|
424
459
|
|
|
@@ -461,6 +496,17 @@ async function pasteSelection() {
|
|
|
461
496
|
}
|
|
462
497
|
}
|
|
463
498
|
|
|
499
|
+
// Create a handler for paste events at the container level
|
|
500
|
+
function handleContainerPaste(event: ClipboardEvent) {
|
|
501
|
+
if (isSearchFocused()) {
|
|
502
|
+
// If search is focused and spreadsheet has a selection, override default behavior
|
|
503
|
+
if (selectionStart.value) {
|
|
504
|
+
event.preventDefault()
|
|
505
|
+
pasteSelection()
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
464
510
|
// Add a variable to track the original value before editing
|
|
465
511
|
const editingOriginalValue = ref<string | null>(null)
|
|
466
512
|
|
|
@@ -543,8 +589,8 @@ function handleCellKeyDown(event: KeyboardEvent, row: number, col: number) {
|
|
|
543
589
|
// Update keyboard shortcuts to include undo/redo
|
|
544
590
|
function handleSpreadsheetKeyDown(event: KeyboardEvent) {
|
|
545
591
|
// Don't intercept keyboard shortcuts when editing a cell
|
|
546
|
-
if (editingCell.value) return
|
|
547
|
-
|
|
592
|
+
if (editingCell.value || isSearchFocused()) return
|
|
593
|
+
console.log('handleSpreadsheetKeyDown', event)
|
|
548
594
|
const isCtrlOrCmd = event.ctrlKey || event.metaKey
|
|
549
595
|
|
|
550
596
|
if (isCtrlOrCmd) {
|
|
@@ -593,6 +639,64 @@ const filteredRows = computed(() => {
|
|
|
593
639
|
})
|
|
594
640
|
})
|
|
595
641
|
})
|
|
642
|
+
|
|
643
|
+
// Add after other ref declarations
|
|
644
|
+
const columnWidths = ref<Map<string, number>>(new Map())
|
|
645
|
+
const isResizing = ref(false)
|
|
646
|
+
const resizingColumn = ref<string | null>(null)
|
|
647
|
+
const startX = ref<number>(0)
|
|
648
|
+
const startWidth = ref<number>(0)
|
|
649
|
+
|
|
650
|
+
// Add after other function declarations
|
|
651
|
+
function handleResizeStart(e: MouseEvent, columnKey: string) {
|
|
652
|
+
isResizing.value = true
|
|
653
|
+
resizingColumn.value = columnKey
|
|
654
|
+
startX.value = e.pageX
|
|
655
|
+
|
|
656
|
+
// Find the column header element
|
|
657
|
+
const columnHeader = (e.target as HTMLElement).closest('th')
|
|
658
|
+
if (!columnHeader) return
|
|
659
|
+
|
|
660
|
+
// Get the actual computed width of the column
|
|
661
|
+
const computedWidth = columnHeader.getBoundingClientRect().width
|
|
662
|
+
const currentWidth = columnWidths.value.get(columnKey) || computedWidth
|
|
663
|
+
startWidth.value = currentWidth
|
|
664
|
+
|
|
665
|
+
// Add event listeners for mousemove and mouseup
|
|
666
|
+
document.addEventListener('mousemove', handleResizeMove)
|
|
667
|
+
document.addEventListener('mouseup', handleResizeEnd)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function handleResizeMove(e: MouseEvent) {
|
|
671
|
+
if (!isResizing.value || !resizingColumn.value) return
|
|
672
|
+
|
|
673
|
+
e.preventDefault()
|
|
674
|
+
|
|
675
|
+
const diff = e.pageX - startX.value
|
|
676
|
+
const newWidth = Math.max(80, startWidth.value + diff) // Keep minimum width of 80px
|
|
677
|
+
|
|
678
|
+
columnWidths.value.set(resizingColumn.value, newWidth)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function handleResizeEnd() {
|
|
682
|
+
isResizing.value = false
|
|
683
|
+
resizingColumn.value = null
|
|
684
|
+
|
|
685
|
+
// Remove event listeners
|
|
686
|
+
document.removeEventListener('mousemove', handleResizeMove)
|
|
687
|
+
document.removeEventListener('mouseup', handleResizeEnd)
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Clean up event listeners when component is unmounted
|
|
691
|
+
onUnmounted(() => {
|
|
692
|
+
document.removeEventListener('mousemove', handleResizeMove)
|
|
693
|
+
document.removeEventListener('mouseup', handleResizeEnd)
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
// Add a computed property for visible column options
|
|
697
|
+
const columnOptions = computed(() => {
|
|
698
|
+
return columns.value.filter(col => !col.hidden)
|
|
699
|
+
})
|
|
596
700
|
</script>
|
|
597
701
|
|
|
598
702
|
<template>
|
|
@@ -600,7 +704,22 @@ const filteredRows = computed(() => {
|
|
|
600
704
|
<div class="flex gap-05 py-05 justify-content-end m_flex-wrap">
|
|
601
705
|
<label v-if="label" class="label me-auto">{{ label }}</label>
|
|
602
706
|
<div class="flex gap-075">
|
|
603
|
-
<
|
|
707
|
+
<Dropdown flat thin icon="view_column">
|
|
708
|
+
<div class="p-05">
|
|
709
|
+
<div class="flex space-between">
|
|
710
|
+
<Btn flat thin small value="Select All" @click="visibleColumns = columnOptions.map(col => col.key)" />
|
|
711
|
+
<Btn flat thin small value="Clear All" @click="visibleColumns = []" />
|
|
712
|
+
</div>
|
|
713
|
+
<CheckInput
|
|
714
|
+
v-for="col in columnOptions"
|
|
715
|
+
:key="col.key"
|
|
716
|
+
v-model="visibleColumns"
|
|
717
|
+
:value="col.key"
|
|
718
|
+
:label="col.label || col.key"
|
|
719
|
+
/>
|
|
720
|
+
</div>
|
|
721
|
+
</Dropdown>
|
|
722
|
+
<TextInput ref="searchInputRef" v-model="search" icon="search" placeholder="Search" class="m-0 max-w200px" />
|
|
604
723
|
<Btn v-tooltip="'Paste'" flat thin round icon="paste" @click="pasteSelection" />
|
|
605
724
|
<Btn v-tooltip="'copy'" flat thin round icon="copy" @click="copySelection" />
|
|
606
725
|
<Btn v-tooltip="'Undo'" flat thin round icon="undo" :disabled="!canUndo" @click="undo" />
|
|
@@ -610,156 +729,57 @@ const filteredRows = computed(() => {
|
|
|
610
729
|
<div class="spreadsheet" @mouseup="handleMouseUp">
|
|
611
730
|
<div class="flex w-100p relative">
|
|
612
731
|
<!-- Fixed Columns -->
|
|
613
|
-
<
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
v-for="col in fixedColumns"
|
|
639
|
-
:key="col.key"
|
|
640
|
-
:class="{
|
|
641
|
-
selected: isCellSelected(rowIndex, fixedColumns.indexOf(col)),
|
|
642
|
-
locked: !isCellEditable(col.key),
|
|
643
|
-
}"
|
|
644
|
-
:style="{ width: col.width }"
|
|
645
|
-
:tabindex="col.hidden ? undefined : 0"
|
|
646
|
-
@mousedown="handleMouseDown(rowIndex, fixedColumns.indexOf(col))"
|
|
647
|
-
@mouseover="handleMouseOver(rowIndex, fixedColumns.indexOf(col))"
|
|
648
|
-
@focusin="handleMouseOver(rowIndex, fixedColumns.indexOf(col))"
|
|
649
|
-
@dblclick="startEditing(rowIndex, fixedColumns.indexOf(col))"
|
|
650
|
-
@keydown="handleCellKeyDown($event, rowIndex, fixedColumns.indexOf(col))"
|
|
651
|
-
>
|
|
652
|
-
<template v-if="editingCell && editingCell.row === rowIndex && editingCell.col === fixedColumns.indexOf(col)">
|
|
653
|
-
<input
|
|
654
|
-
:ref="el => setInputRef(el, `cell-${rowIndex}-${fixedColumns.indexOf(col)}`)"
|
|
655
|
-
:value="row[col.key]"
|
|
656
|
-
type="text"
|
|
657
|
-
class="spreadsheet-input"
|
|
658
|
-
@input="(e: Event) => updateCell(rowIndex, col.key, (e.target as HTMLInputElement).value)"
|
|
659
|
-
@blur="handleStopEditingAndBlur(false)"
|
|
660
|
-
@keydown.enter.prevent="handleStopEditingAndBlur(false)"
|
|
661
|
-
@keydown.esc.prevent="handleStopEditingAndBlur(true)"
|
|
662
|
-
@mousedown.stop
|
|
663
|
-
>
|
|
664
|
-
<span class="spreadsheet-cell spreadsheetCellPlaceHolder">{{ formatCellValue(row[col.key], col.format) }}</span>
|
|
665
|
-
</template>
|
|
666
|
-
<template v-else>
|
|
667
|
-
<template v-if="col.format === 'image'">
|
|
668
|
-
<div class="h40px w-100p flex align-items-center justify-content-center overflow-hidden">
|
|
669
|
-
<img class=" w-100p h-100p contain radius-05" :src="row[col.key]" :alt="col.label || col.key">
|
|
670
|
-
</div>
|
|
671
|
-
</template>
|
|
672
|
-
<template v-else-if="col.format === 'boolean'">
|
|
673
|
-
<CheckInput
|
|
674
|
-
:modelValue="!!row[col.key]"
|
|
675
|
-
:disabled="!isCellEditable(col.key)"
|
|
676
|
-
@update:modelValue="(value: boolean | any[] | undefined) => updateCell(rowIndex, col.key, !!value)"
|
|
677
|
-
@mousedown.stop
|
|
678
|
-
/>
|
|
679
|
-
</template>
|
|
680
|
-
<template v-else>
|
|
681
|
-
<span class="spreadsheet-cell">{{ formatCellValue(row[col.key], col.format) }}</span>
|
|
682
|
-
</template>
|
|
683
|
-
</template>
|
|
684
|
-
</td>
|
|
685
|
-
</tr>
|
|
686
|
-
</tbody>
|
|
687
|
-
</table>
|
|
732
|
+
<SpreadsheetTable
|
|
733
|
+
v-if="fixedColumns.length"
|
|
734
|
+
:columns="fixedColumns"
|
|
735
|
+
:rows="filteredRows"
|
|
736
|
+
:is-fixed="true"
|
|
737
|
+
:show-row-numbers="true"
|
|
738
|
+
:column-widths="columnWidths"
|
|
739
|
+
:sort-column="sortColumn"
|
|
740
|
+
:sort-direction="sortDirection"
|
|
741
|
+
:selection-start="selectionStart"
|
|
742
|
+
:selection-end="selectionEnd"
|
|
743
|
+
:editing-cell="editingCell"
|
|
744
|
+
:base-column-index="0"
|
|
745
|
+
class="sticky z-2 start-0 bg-white"
|
|
746
|
+
@sortColumn="sortByColumn"
|
|
747
|
+
@resizeStart="handleResizeStart"
|
|
748
|
+
@selectRow="selectEntireRow"
|
|
749
|
+
@cellMouseDown="handleMouseDown"
|
|
750
|
+
@cellMouseOver="handleMouseOver"
|
|
751
|
+
@cellEditStart="startEditing"
|
|
752
|
+
@cellKeyDown="handleCellKeyDown"
|
|
753
|
+
@updateCell="updateCell"
|
|
754
|
+
@stopEditing="handleStopEditingAndBlur"
|
|
755
|
+
@setInputRef="setInputRef"
|
|
756
|
+
/>
|
|
688
757
|
|
|
689
758
|
<!-- Scrollable Columns -->
|
|
690
759
|
<div class="flex-shrink flex-grow overflow-x">
|
|
691
|
-
<
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
:key="col.key"
|
|
715
|
-
:class="{
|
|
716
|
-
selected: isCellSelected(rowIndex, fixedColumns.length + colIndex),
|
|
717
|
-
locked: !isCellEditable(col.key),
|
|
718
|
-
}"
|
|
719
|
-
:style="{ width: col.width }"
|
|
720
|
-
:tabindex="col.hidden ? undefined : 0"
|
|
721
|
-
@mousedown="handleMouseDown(rowIndex, fixedColumns.length + colIndex)"
|
|
722
|
-
@mouseover="handleMouseOver(rowIndex, fixedColumns.length + colIndex)"
|
|
723
|
-
@focusin="handleMouseOver(rowIndex, fixedColumns.length + colIndex)"
|
|
724
|
-
@dblclick="startEditing(rowIndex, fixedColumns.length + colIndex)"
|
|
725
|
-
@keydown="handleCellKeyDown($event, rowIndex, fixedColumns.length + colIndex)"
|
|
726
|
-
>
|
|
727
|
-
<template v-if="editingCell && editingCell.row === rowIndex && editingCell.col === (fixedColumns.length + colIndex)">
|
|
728
|
-
<input
|
|
729
|
-
:ref="el => setInputRef(el, `cell-${rowIndex}-${fixedColumns.length + colIndex}`)"
|
|
730
|
-
:value="row[col.key]"
|
|
731
|
-
type="text"
|
|
732
|
-
class="spreadsheet-input"
|
|
733
|
-
@input="(e: Event) => updateCell(rowIndex, col.key, (e.target as HTMLInputElement).value)"
|
|
734
|
-
@blur="handleStopEditingAndBlur(false)"
|
|
735
|
-
@keydown.enter.prevent="handleStopEditingAndBlur(false)"
|
|
736
|
-
@keydown.esc.prevent="handleStopEditingAndBlur(true)"
|
|
737
|
-
@mousedown.stop
|
|
738
|
-
>
|
|
739
|
-
<span class="spreadsheet-cell spreadsheetCellPlaceHolder">{{ formatCellValue(row[col.key], col.format) }}</span>
|
|
740
|
-
</template>
|
|
741
|
-
<template v-else>
|
|
742
|
-
<template v-if="col.format === 'image'">
|
|
743
|
-
<div v-if="row[col.key]" class="h40px w-100p flex align-items-center justify-content-center overflow-hidden">
|
|
744
|
-
<img class=" w-100p h-100p contain radius-05" :src="row[col.key]" :alt="col.label || col.key">
|
|
745
|
-
</div>
|
|
746
|
-
</template>
|
|
747
|
-
<template v-else-if="col.format === 'boolean'">
|
|
748
|
-
<CheckInput
|
|
749
|
-
:modelValue="!!row[col.key]"
|
|
750
|
-
:disabled="!isCellEditable(col.key)"
|
|
751
|
-
@update:modelValue="(value: boolean | any[] | undefined) => updateCell(rowIndex, col.key, !!value)"
|
|
752
|
-
@mousedown.stop
|
|
753
|
-
/>
|
|
754
|
-
</template>
|
|
755
|
-
<template v-else>
|
|
756
|
-
<span class="spreadsheet-cell">{{ formatCellValue(row[col.key], col.format) }}</span>
|
|
757
|
-
</template>
|
|
758
|
-
</template>
|
|
759
|
-
</td>
|
|
760
|
-
</tr>
|
|
761
|
-
</tbody>
|
|
762
|
-
</table>
|
|
760
|
+
<SpreadsheetTable
|
|
761
|
+
:columns="scrollableColumns"
|
|
762
|
+
:rows="filteredRows"
|
|
763
|
+
:is-fixed="false"
|
|
764
|
+
:show-row-numbers="!fixedColumns.length"
|
|
765
|
+
:column-widths="columnWidths"
|
|
766
|
+
:sort-column="sortColumn"
|
|
767
|
+
:sort-direction="sortDirection"
|
|
768
|
+
:selection-start="selectionStart"
|
|
769
|
+
:selection-end="selectionEnd"
|
|
770
|
+
:editing-cell="editingCell"
|
|
771
|
+
:base-column-index="fixedColumns.length"
|
|
772
|
+
@sortColumn="sortByColumn"
|
|
773
|
+
@resizeStart="handleResizeStart"
|
|
774
|
+
@selectRow="selectEntireRow"
|
|
775
|
+
@cellMouseDown="handleMouseDown"
|
|
776
|
+
@cellMouseOver="handleMouseOver"
|
|
777
|
+
@cellEditStart="startEditing"
|
|
778
|
+
@cellKeyDown="handleCellKeyDown"
|
|
779
|
+
@updateCell="updateCell"
|
|
780
|
+
@stopEditing="handleStopEditingAndBlur"
|
|
781
|
+
@setInputRef="setInputRef"
|
|
782
|
+
/>
|
|
763
783
|
</div>
|
|
764
784
|
</div>
|
|
765
785
|
<Btn v-if="allowAddRow" outline thin round icon="add" value="Add Row" class="mt-05" @click="addRow" />
|
|
@@ -768,97 +788,8 @@ const filteredRows = computed(() => {
|
|
|
768
788
|
</template>
|
|
769
789
|
|
|
770
790
|
<style scoped>
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
}
|
|
774
|
-
.spreadsheet table {
|
|
775
|
-
border-collapse: collapse;
|
|
776
|
-
}
|
|
777
|
-
.spreadsheet th, .spreadsheet td {
|
|
778
|
-
border: 1px solid var(--border-color);
|
|
779
|
-
padding: 0.1rem 0.5rem;
|
|
780
|
-
min-width: 80px;
|
|
781
|
-
background: var(--bgl-white);
|
|
782
|
-
user-select: none;
|
|
783
|
-
}
|
|
784
|
-
.spreadsheet th {
|
|
785
|
-
background: var(--input-bg);
|
|
786
|
-
white-space: nowrap;
|
|
787
|
-
position: relative;
|
|
788
|
-
padding: 0.25rem 0.5rem;
|
|
789
|
-
font-weight: 500;
|
|
790
|
-
text-align: start;
|
|
791
|
-
}
|
|
792
|
-
.spreadsheet th .bgl_icon-font{
|
|
793
|
-
vertical-align: middle;
|
|
794
|
-
}
|
|
795
|
-
.spreadsheet td.selected {
|
|
796
|
-
background: var(--bgl-primary-light);
|
|
797
|
-
}
|
|
798
|
-
.spreadsheet td.locked {
|
|
799
|
-
background: var(--bgl-gray-light);
|
|
800
|
-
cursor: default;
|
|
801
|
-
}
|
|
802
|
-
.spreadsheet td.locked.selected {
|
|
803
|
-
background: var(--bgl-primary-light);
|
|
804
|
-
}
|
|
805
|
-
.spreadsheet td {
|
|
806
|
-
height: 40px;
|
|
807
|
-
vertical-align: middle;
|
|
808
|
-
}
|
|
809
|
-
.spreadsheet td:has(img){
|
|
810
|
-
padding: 0;
|
|
811
|
-
}
|
|
812
|
-
.spreadsheet td span{
|
|
813
|
-
display: block;
|
|
814
|
-
display: -webkit-box;
|
|
815
|
-
max-width: 100%;
|
|
816
|
-
-webkit-box-orient: vertical;
|
|
817
|
-
overflow: hidden;
|
|
818
|
-
text-overflow: ellipsis;
|
|
819
|
-
-webkit-line-clamp: 1;
|
|
820
|
-
word-break: break-all;
|
|
821
|
-
}
|
|
822
|
-
.spreadsheet input {
|
|
823
|
-
width: 100%;
|
|
824
|
-
border: none;
|
|
825
|
-
background: transparent;
|
|
826
|
-
padding: 0;
|
|
827
|
-
margin: 0;
|
|
828
|
-
min-height: 0;
|
|
829
|
-
min-width: 0;
|
|
830
|
-
}
|
|
831
|
-
.spreadsheet input:focus {
|
|
832
|
-
outline: 2px solid var(--bgl-primary);
|
|
833
|
-
outline-offset: 6px;
|
|
834
|
-
}
|
|
835
|
-
.spreadsheet th.sortable {
|
|
836
|
-
cursor: pointer;
|
|
837
|
-
}
|
|
838
|
-
.row-number-header, .row-number {
|
|
839
|
-
width: fit-content;
|
|
840
|
-
min-width: fit-content !important;
|
|
841
|
-
padding: 0.1rem 0.7rem !important;
|
|
842
|
-
}
|
|
843
|
-
.spreadsheet td .bgl-checkbox{
|
|
844
|
-
margin: 0;
|
|
845
|
-
text-align: center;
|
|
846
|
-
justify-content: center;
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
.spreadsheet td:has(.bgl-checkbox){
|
|
850
|
-
text-align: center;
|
|
851
|
-
background: var(--input-bg);
|
|
852
|
-
}
|
|
853
|
-
.spreadsheet td:has(:checked){
|
|
854
|
-
background: var(--bgl-primary-light);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
.spreadsheetCellPlaceHolder{
|
|
858
|
-
height: 0px;
|
|
859
|
-
overflow: hidden;
|
|
860
|
-
opacity: 0;
|
|
861
|
-
pointer-events: none;
|
|
791
|
+
/* Spreadsheet container styles */
|
|
792
|
+
.spreadsheet {
|
|
862
793
|
user-select: none;
|
|
863
794
|
}
|
|
864
795
|
</style>
|