@morscherlab/mint-sdk 1.0.45 → 1.0.46
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/DataFrame.vue.d.ts +15 -0
- package/dist/components/index.js +1 -1
- package/dist/{components-DjfatNJv.js → components-C7UFkNIp.js} +187 -86
- package/dist/components-C7UFkNIp.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/install.js +1 -1
- package/dist/styles.css +140 -0
- package/package.json +1 -1
- package/src/__tests__/components/DataFrame.test.ts +100 -0
- package/src/components/DataFrame.vue +97 -5
- package/src/styles/components/dataframe.css +79 -0
- package/dist/components-DjfatNJv.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as LoadingSpinner_default, A as ReagentEditor_default, B as Avatar_default, C as formatSequenceRemaining, Ct as Calendar_default, D as ScientificNumber_default, Dt as ColorSlider_default, E as ChemicalFormula_default, Et as BaseTabs_default, F as BioTemplatePackWorkspaceView_default, G as DoseDesignWorkspaceView_default, H as StatusIndicator_default, I as BioTemplateExperimentWorkspaceView_default, J as ScheduleCalendar_default, K as ControlWorkspaceView_default, L as BioTemplateRenderer_default, M as GroupAssigner_default, N as RackEditor_default, O as ProtocolStepEditor_default, P as BioTemplatePresetWorkspaceView_default, Q as SmartGroupFieldRecipe_default, R as ChartContainer_default, S as formatSequenceEta, St as DataFrame_default, T as sequenceSamplesRemaining, Tt as SegmentedControl_default, U as Divider_default, V as ProgressBar_default, W as AppContainer_default, X as AutoGroupModal_default, Y as SampleSelector_default, Z as SmartGroupManual_default, _ as InstrumentStateBadge_default, _t as CollapsibleCard_default, a as ExperimentDataViewer_default, at as DoseCalculator_default, b as estimateSequenceFinishDate, bt as AppToastContainer_default, c as LcmsSequenceTable_default, ct as AppLayout_default, d as extractLcmsCommonPrefix, dt as FormActions_default, et as ReagentList_default, f as extractLcmsSampleName, ft as StepWizard_default, g as InstrumentStatusCard_default, gt as PluginIcon_default, h as reconstructLcmsPlateCellsFromSequenceItems, ht as AppAvatarMenu_default, i as TimeRangeInput_default, it as ExperimentTimeline_default, j as SmartGroupModal_default, k as SampleHierarchyTree_default, l as DEFAULT_LCMS_SEQUENCE_COLUMNS, lt as AppSidebar_default, m as lcmsWellIdFromPosition, mt as AppPluginSwitcher_default, n as FitPanel_default, nt as SampleLegend_default, o as BatchProgressList_default, ot as PluginWorkspaceView_default, p as inferLcmsPlateTypeFromWellIds, pt as AppTopBar_default, q as ComponentBindingRenderer_default, r as ResourceCard_default, rt as WellPlate_default, s as AuditTrail_default, st as MobileSupportGate_default, tt as PlateMapEditor_default, u as basenameFromWindowsPath, ut as FormBuilder_default, v as InstrumentAlertLog_default, vt as ThemeToggle_default, w as sequenceProgressPercent, wt as DropdownButton_default, x as estimateSequenceRemainingSeconds, xt as AlertBox_default, y as SequenceProgressBar_default, yt as IconButton_default, z as Breadcrumb_default } from "./components-
|
|
1
|
+
import { $ as LoadingSpinner_default, A as ReagentEditor_default, B as Avatar_default, C as formatSequenceRemaining, Ct as Calendar_default, D as ScientificNumber_default, Dt as ColorSlider_default, E as ChemicalFormula_default, Et as BaseTabs_default, F as BioTemplatePackWorkspaceView_default, G as DoseDesignWorkspaceView_default, H as StatusIndicator_default, I as BioTemplateExperimentWorkspaceView_default, J as ScheduleCalendar_default, K as ControlWorkspaceView_default, L as BioTemplateRenderer_default, M as GroupAssigner_default, N as RackEditor_default, O as ProtocolStepEditor_default, P as BioTemplatePresetWorkspaceView_default, Q as SmartGroupFieldRecipe_default, R as ChartContainer_default, S as formatSequenceEta, St as DataFrame_default, T as sequenceSamplesRemaining, Tt as SegmentedControl_default, U as Divider_default, V as ProgressBar_default, W as AppContainer_default, X as AutoGroupModal_default, Y as SampleSelector_default, Z as SmartGroupManual_default, _ as InstrumentStateBadge_default, _t as CollapsibleCard_default, a as ExperimentDataViewer_default, at as DoseCalculator_default, b as estimateSequenceFinishDate, bt as AppToastContainer_default, c as LcmsSequenceTable_default, ct as AppLayout_default, d as extractLcmsCommonPrefix, dt as FormActions_default, et as ReagentList_default, f as extractLcmsSampleName, ft as StepWizard_default, g as InstrumentStatusCard_default, gt as PluginIcon_default, h as reconstructLcmsPlateCellsFromSequenceItems, ht as AppAvatarMenu_default, i as TimeRangeInput_default, it as ExperimentTimeline_default, j as SmartGroupModal_default, k as SampleHierarchyTree_default, l as DEFAULT_LCMS_SEQUENCE_COLUMNS, lt as AppSidebar_default, m as lcmsWellIdFromPosition, mt as AppPluginSwitcher_default, n as FitPanel_default, nt as SampleLegend_default, o as BatchProgressList_default, ot as PluginWorkspaceView_default, p as inferLcmsPlateTypeFromWellIds, pt as AppTopBar_default, q as ComponentBindingRenderer_default, r as ResourceCard_default, rt as WellPlate_default, s as AuditTrail_default, st as MobileSupportGate_default, tt as PlateMapEditor_default, u as basenameFromWindowsPath, ut as FormBuilder_default, v as InstrumentAlertLog_default, vt as ThemeToggle_default, w as sequenceProgressPercent, wt as DropdownButton_default, x as estimateSequenceRemainingSeconds, xt as AlertBox_default, y as SequenceProgressBar_default, yt as IconButton_default, z as Breadcrumb_default } from "./components-C7UFkNIp.js";
|
|
2
2
|
import { t as BaseButton_default } from "./BaseButton-Dgqrze41.js";
|
|
3
3
|
import { r as BaseInput_default, t as BaseSelect_default } from "./BaseSelect-ekgr9fDo.js";
|
|
4
4
|
import { $ as parseTime, A as useConcentrationUnits, B as TagsInput_default, C as controlsToViewIds, D as getTypeDefault, E as getFieldRegistryEntry, F as ATOMIC_WEIGHTS, G as findAvailableSlots, H as addMinutes, I as useChemicalFormula, J as formatTime, K as findNearestTimeSlotIndex, L as DateTimePicker_default, M as SequenceInput_default, N as useSequenceUtils, O as ConcentrationInput_default, P as FormulaInput_default, Q as isTimeInRange, R as FileUploader_default, S as controlsToTopBarSettingsConfig, T as getDefaultControlView, U as compareTime, V as TimePicker_default, W as durationMinutes, X as fromMinutes, Y as formatTimeSlot, Z as generateTimeSlots, _ as controlsToFormSchema, a as defineDoseCalculatorControlProps, at as MultiSelect_default, b as controlsToSettingsSchema, c as defineWellPlateDoseComponentBindings, ct as BaseSlider_default, d as defineControlModel, dt as BaseCheckbox_default, et as rangesOverlap, ft as Tooltip_default, h as controlValuesToComponentProps, i as useControlSchema, it as DatePicker_default, j as MoleculeInput_default, k as UnitInput_default, l as defineWellPlateDoseControlProps, lt as BaseRadioGroup_default, m as controlValuesToComponentBindingsById, n as defineControls, nt as toMinutes, o as defineDoseDesignControlModel, ot as useListSelection, p as controlValuesToComponentBindings, pt as BaseTextarea_default, q as formatDuration, r as getControlDefaults, rt as useTimeUtils, s as defineWellPlateControlProps, st as useSelectionLimit, t as defineControlComponentBindings, tt as snapToSlot, u as useControlWorkspace, ut as BaseToggle_default, v as controlsToSectionFormSchema, w as controlsToViewItems, x as controlsToSidebarPanels, y as controlsToSectionFormSchemas, z as NumberInput_default } from "./useControlSchema-BZNdalmL.js";
|
package/dist/install.js
CHANGED
package/dist/styles.css
CHANGED
|
@@ -6141,6 +6141,77 @@ to { transform: rotate(360deg);
|
|
|
6141
6141
|
width: 0.875rem;
|
|
6142
6142
|
height: 0.875rem;
|
|
6143
6143
|
}
|
|
6144
|
+
|
|
6145
|
+
/* Resizable columns */
|
|
6146
|
+
.mint-dataframe__th--resizable {
|
|
6147
|
+
position: relative;
|
|
6148
|
+
}
|
|
6149
|
+
.mint-dataframe__resize-handle {
|
|
6150
|
+
position: absolute;
|
|
6151
|
+
top: 0;
|
|
6152
|
+
right: 0;
|
|
6153
|
+
width: 7px;
|
|
6154
|
+
height: 100%;
|
|
6155
|
+
cursor: col-resize;
|
|
6156
|
+
user-select: none;
|
|
6157
|
+
touch-action: none;
|
|
6158
|
+
z-index: 1;
|
|
6159
|
+
}
|
|
6160
|
+
.mint-dataframe__resize-handle::after {
|
|
6161
|
+
content: '';
|
|
6162
|
+
position: absolute;
|
|
6163
|
+
top: 20%;
|
|
6164
|
+
right: 3px;
|
|
6165
|
+
width: 1px;
|
|
6166
|
+
height: 60%;
|
|
6167
|
+
background-color: var(--border-color);
|
|
6168
|
+
transition: background-color 0.15s ease;
|
|
6169
|
+
}
|
|
6170
|
+
.mint-dataframe__resize-handle:hover::after {
|
|
6171
|
+
width: 2px;
|
|
6172
|
+
background-color: var(--color-primary);
|
|
6173
|
+
}
|
|
6174
|
+
|
|
6175
|
+
/* Row delete action column */
|
|
6176
|
+
.mint-dataframe__th--actions,
|
|
6177
|
+
.mint-dataframe__td--actions {
|
|
6178
|
+
width: 2.75rem;
|
|
6179
|
+
text-align: center !important;
|
|
6180
|
+
white-space: nowrap;
|
|
6181
|
+
}
|
|
6182
|
+
.mint-dataframe__delete-btn {
|
|
6183
|
+
display: inline-flex;
|
|
6184
|
+
align-items: center;
|
|
6185
|
+
justify-content: center;
|
|
6186
|
+
width: 1.5rem;
|
|
6187
|
+
height: 1.5rem;
|
|
6188
|
+
border: 0;
|
|
6189
|
+
border-radius: var(--radius-md);
|
|
6190
|
+
background: transparent;
|
|
6191
|
+
color: var(--text-muted);
|
|
6192
|
+
cursor: pointer;
|
|
6193
|
+
opacity: 0;
|
|
6194
|
+
transition:
|
|
6195
|
+
background-color 0.15s ease,
|
|
6196
|
+
color 0.15s ease,
|
|
6197
|
+
opacity 0.15s ease;
|
|
6198
|
+
}
|
|
6199
|
+
.mint-dataframe__row:hover .mint-dataframe__delete-btn,
|
|
6200
|
+
.mint-dataframe__delete-btn:focus-visible {
|
|
6201
|
+
opacity: 1;
|
|
6202
|
+
}
|
|
6203
|
+
.mint-dataframe__delete-btn:hover {
|
|
6204
|
+
background-color: color-mix(in srgb, var(--color-error, #ef4444) 14%, transparent);
|
|
6205
|
+
color: var(--color-error, #ef4444);
|
|
6206
|
+
}
|
|
6207
|
+
.mint-dataframe__delete-btn:focus-visible {
|
|
6208
|
+
outline: none;
|
|
6209
|
+
box-shadow: var(--focus-ring);
|
|
6210
|
+
}
|
|
6211
|
+
.mint-dataframe__delete-btn svg {
|
|
6212
|
+
width: 0.875rem;
|
|
6213
|
+
height: 0.875rem;
|
|
6214
|
+
}
|
|
6144
6215
|
/* AlertBox Component Styles */
|
|
6145
6216
|
.mint-alert {
|
|
6146
6217
|
display: flex;
|
|
@@ -29680,6 +29751,75 @@ html.dark .mint-dataframe__loading {
|
|
|
29680
29751
|
width: 0.875rem;
|
|
29681
29752
|
height: 0.875rem;
|
|
29682
29753
|
}
|
|
29754
|
+
/* Resizable columns */
|
|
29755
|
+
.mint-dataframe__th--resizable {
|
|
29756
|
+
position: relative;
|
|
29757
|
+
}
|
|
29758
|
+
.mint-dataframe__resize-handle {
|
|
29759
|
+
position: absolute;
|
|
29760
|
+
top: 0;
|
|
29761
|
+
right: 0;
|
|
29762
|
+
width: 7px;
|
|
29763
|
+
height: 100%;
|
|
29764
|
+
cursor: col-resize;
|
|
29765
|
+
user-select: none;
|
|
29766
|
+
touch-action: none;
|
|
29767
|
+
z-index: 1;
|
|
29768
|
+
}
|
|
29769
|
+
.mint-dataframe__resize-handle::after {
|
|
29770
|
+
content: '';
|
|
29771
|
+
position: absolute;
|
|
29772
|
+
top: 20%;
|
|
29773
|
+
right: 3px;
|
|
29774
|
+
width: 1px;
|
|
29775
|
+
height: 60%;
|
|
29776
|
+
background-color: var(--border-color);
|
|
29777
|
+
transition: background-color 0.15s ease;
|
|
29778
|
+
}
|
|
29779
|
+
.mint-dataframe__resize-handle:hover::after {
|
|
29780
|
+
width: 2px;
|
|
29781
|
+
background-color: var(--color-primary);
|
|
29782
|
+
}
|
|
29783
|
+
/* Row delete action column */
|
|
29784
|
+
.mint-dataframe__th--actions,
|
|
29785
|
+
.mint-dataframe__td--actions {
|
|
29786
|
+
width: 2.75rem;
|
|
29787
|
+
text-align: center !important;
|
|
29788
|
+
white-space: nowrap;
|
|
29789
|
+
}
|
|
29790
|
+
.mint-dataframe__delete-btn {
|
|
29791
|
+
display: inline-flex;
|
|
29792
|
+
align-items: center;
|
|
29793
|
+
justify-content: center;
|
|
29794
|
+
width: 1.5rem;
|
|
29795
|
+
height: 1.5rem;
|
|
29796
|
+
border: 0;
|
|
29797
|
+
border-radius: var(--radius-md);
|
|
29798
|
+
background: transparent;
|
|
29799
|
+
color: var(--text-muted);
|
|
29800
|
+
cursor: pointer;
|
|
29801
|
+
opacity: 0;
|
|
29802
|
+
transition:
|
|
29803
|
+
background-color 0.15s ease,
|
|
29804
|
+
color 0.15s ease,
|
|
29805
|
+
opacity 0.15s ease;
|
|
29806
|
+
}
|
|
29807
|
+
.mint-dataframe__row:hover .mint-dataframe__delete-btn,
|
|
29808
|
+
.mint-dataframe__delete-btn:focus-visible {
|
|
29809
|
+
opacity: 1;
|
|
29810
|
+
}
|
|
29811
|
+
.mint-dataframe__delete-btn:hover {
|
|
29812
|
+
background-color: color-mix(in srgb, var(--color-error, #ef4444) 14%, transparent);
|
|
29813
|
+
color: var(--color-error, #ef4444);
|
|
29814
|
+
}
|
|
29815
|
+
.mint-dataframe__delete-btn:focus-visible {
|
|
29816
|
+
outline: none;
|
|
29817
|
+
box-shadow: var(--focus-ring);
|
|
29818
|
+
}
|
|
29819
|
+
.mint-dataframe__delete-btn svg {
|
|
29820
|
+
width: 0.875rem;
|
|
29821
|
+
height: 0.875rem;
|
|
29822
|
+
}
|
|
29683
29823
|
/* WellEditPopup Component Styles */
|
|
29684
29824
|
.mint-well-edit-popup__backdrop {
|
|
29685
29825
|
position: fixed;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morscherlab/mint-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.46",
|
|
4
4
|
"description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -812,4 +812,104 @@ describe('DataFrame', () => {
|
|
|
812
812
|
expect(wrapper.emitted('row-click')).toBeUndefined()
|
|
813
813
|
})
|
|
814
814
|
})
|
|
815
|
+
|
|
816
|
+
describe('row deletion', () => {
|
|
817
|
+
it('should not show an actions column by default', () => {
|
|
818
|
+
const wrapper = mount(DataFrame, {
|
|
819
|
+
props: { data: mockData, columns: mockColumns },
|
|
820
|
+
})
|
|
821
|
+
expect(wrapper.find('.mint-dataframe__td--actions').exists()).toBe(false)
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
it('should show a delete button per row when deletable', () => {
|
|
825
|
+
const wrapper = mount(DataFrame, {
|
|
826
|
+
props: { data: mockData, columns: mockColumns, deletable: true },
|
|
827
|
+
})
|
|
828
|
+
expect(wrapper.find('.mint-dataframe__th--actions').exists()).toBe(true)
|
|
829
|
+
expect(wrapper.findAll('.mint-dataframe__delete-btn')).toHaveLength(mockData.length)
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
it('should emit delete-row with the row and index', async () => {
|
|
833
|
+
const wrapper = mount(DataFrame, {
|
|
834
|
+
props: { data: mockData, columns: mockColumns, deletable: true },
|
|
835
|
+
})
|
|
836
|
+
await wrapper.findAll('.mint-dataframe__delete-btn')[1].trigger('click')
|
|
837
|
+
|
|
838
|
+
expect(wrapper.emitted('delete-row')).toHaveLength(1)
|
|
839
|
+
const [row, index] = wrapper.emitted('delete-row')?.[0] as [Record<string, unknown>, number]
|
|
840
|
+
expect(row.name).toBe('Bob')
|
|
841
|
+
expect(index).toBe(1)
|
|
842
|
+
})
|
|
843
|
+
|
|
844
|
+
it('should not emit row-click when the delete button is clicked', async () => {
|
|
845
|
+
const wrapper = mount(DataFrame, {
|
|
846
|
+
props: { data: mockData, columns: mockColumns, deletable: true },
|
|
847
|
+
})
|
|
848
|
+
await wrapper.find('.mint-dataframe__delete-btn').trigger('click')
|
|
849
|
+
expect(wrapper.emitted('row-click')).toBeUndefined()
|
|
850
|
+
})
|
|
851
|
+
|
|
852
|
+
it('should use a custom delete label', () => {
|
|
853
|
+
const wrapper = mount(DataFrame, {
|
|
854
|
+
props: { data: mockData, columns: mockColumns, deletable: true, deleteLabel: 'Remove' },
|
|
855
|
+
})
|
|
856
|
+
expect(wrapper.find('.mint-dataframe__delete-btn').attributes('aria-label')).toBe('Remove')
|
|
857
|
+
})
|
|
858
|
+
})
|
|
859
|
+
|
|
860
|
+
describe('column resizing', () => {
|
|
861
|
+
it('should not show resize handles by default', () => {
|
|
862
|
+
const wrapper = mount(DataFrame, {
|
|
863
|
+
props: { data: mockData, columns: mockColumns },
|
|
864
|
+
})
|
|
865
|
+
expect(wrapper.find('.mint-dataframe__resize-handle').exists()).toBe(false)
|
|
866
|
+
})
|
|
867
|
+
|
|
868
|
+
it('should show a resize handle per column when resizable', () => {
|
|
869
|
+
const wrapper = mount(DataFrame, {
|
|
870
|
+
props: { data: mockData, columns: mockColumns, resizable: true },
|
|
871
|
+
})
|
|
872
|
+
expect(wrapper.findAll('.mint-dataframe__resize-handle')).toHaveLength(mockColumns.length)
|
|
873
|
+
})
|
|
874
|
+
|
|
875
|
+
it('should emit update:columnWidths while dragging a handle', async () => {
|
|
876
|
+
const wrapper = mount(DataFrame, {
|
|
877
|
+
props: { data: mockData, columns: mockColumns, resizable: true },
|
|
878
|
+
})
|
|
879
|
+
const handle = wrapper.findAll('.mint-dataframe__resize-handle')[0]
|
|
880
|
+
await handle.trigger('mousedown', { clientX: 100 })
|
|
881
|
+
window.dispatchEvent(new MouseEvent('mousemove', { clientX: 220 }))
|
|
882
|
+
window.dispatchEvent(new MouseEvent('mouseup'))
|
|
883
|
+
|
|
884
|
+
const events = wrapper.emitted('update:columnWidths')
|
|
885
|
+
expect(events).toBeTruthy()
|
|
886
|
+
const widths = events?.[events.length - 1]?.[0] as Record<string, number>
|
|
887
|
+
expect(widths.name).toBeGreaterThanOrEqual(48) // MIN_COL_WIDTH floor
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
it('should honor controlled columnWidths', () => {
|
|
891
|
+
const wrapper = mount(DataFrame, {
|
|
892
|
+
props: {
|
|
893
|
+
data: mockData,
|
|
894
|
+
columns: mockColumns,
|
|
895
|
+
resizable: true,
|
|
896
|
+
columnWidths: { name: 321 },
|
|
897
|
+
},
|
|
898
|
+
})
|
|
899
|
+
const header = wrapper.find('.mint-dataframe__th')
|
|
900
|
+
expect(header.attributes('style')).toContain('width: 321px')
|
|
901
|
+
})
|
|
902
|
+
|
|
903
|
+
it('should not trigger sort when the resize handle is clicked', async () => {
|
|
904
|
+
const wrapper = mount(DataFrame, {
|
|
905
|
+
props: {
|
|
906
|
+
data: mockData,
|
|
907
|
+
columns: [{ key: 'name', label: 'Name', sortable: true }],
|
|
908
|
+
resizable: true,
|
|
909
|
+
},
|
|
910
|
+
})
|
|
911
|
+
await wrapper.find('.mint-dataframe__resize-handle').trigger('click')
|
|
912
|
+
expect(wrapper.emitted('update:sort')).toBeUndefined()
|
|
913
|
+
})
|
|
914
|
+
})
|
|
815
915
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Data table with sortable columns, search, pagination, and optional row selection. */
|
|
3
|
-
import { ref, computed } from 'vue'
|
|
3
|
+
import { ref, computed, onBeforeUnmount } from 'vue'
|
|
4
4
|
import type { DataFrameColumn, SortState, PaginationState } from '../types'
|
|
5
5
|
import { useSortedItems } from '../composables/useSortedItems'
|
|
6
6
|
import { useTextSearch } from '../composables/useTextSearch'
|
|
@@ -62,6 +62,14 @@ interface Props {
|
|
|
62
62
|
selectedKeys?: (string | number)[]
|
|
63
63
|
/** Make each body row act as a click/keyboard target. */
|
|
64
64
|
clickableRows?: boolean
|
|
65
|
+
/** Show a trailing action column with a per-row delete button. */
|
|
66
|
+
deletable?: boolean
|
|
67
|
+
/** Tooltip / aria-label for the row delete button. */
|
|
68
|
+
deleteLabel?: string
|
|
69
|
+
/** Allow dragging a column's right edge to resize it. */
|
|
70
|
+
resizable?: boolean
|
|
71
|
+
/** Controlled column widths in px, keyed by column key (omit for uncontrolled). */
|
|
72
|
+
columnWidths?: Record<string, number>
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -78,6 +86,9 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
78
86
|
selectable: false,
|
|
79
87
|
selectedKeys: () => [],
|
|
80
88
|
clickableRows: false,
|
|
89
|
+
deletable: false,
|
|
90
|
+
deleteLabel: 'Delete row',
|
|
91
|
+
resizable: false,
|
|
81
92
|
})
|
|
82
93
|
|
|
83
94
|
/**
|
|
@@ -91,8 +102,10 @@ const emit = defineEmits<{
|
|
|
91
102
|
'update:sort': [sort: SortState | null]
|
|
92
103
|
'update:pagination': [pagination: PaginationState]
|
|
93
104
|
'update:selectedKeys': [keys: (string | number)[]]
|
|
105
|
+
'update:columnWidths': [widths: Record<string, number>]
|
|
94
106
|
'row-click': [row: Record<string, unknown>, index: number]
|
|
95
107
|
'cell-click': [value: unknown, column: DataFrameColumn, row: Record<string, unknown>]
|
|
108
|
+
'delete-row': [row: Record<string, unknown>, index: number]
|
|
96
109
|
}>()
|
|
97
110
|
|
|
98
111
|
const searchQuery = ref('')
|
|
@@ -234,6 +247,57 @@ function toggleRowSelect(key: string | number) {
|
|
|
234
247
|
emit('update:selectedKeys', rowSelection.toggleValue(key))
|
|
235
248
|
}
|
|
236
249
|
|
|
250
|
+
// Column resizing — uncontrolled by default; pass `columnWidths` to control it.
|
|
251
|
+
const MIN_COL_WIDTH = 48
|
|
252
|
+
const internalWidths = ref<Record<string, number>>({})
|
|
253
|
+
const activeWidths = computed(() => props.columnWidths ?? internalWidths.value)
|
|
254
|
+
|
|
255
|
+
function toCssSize(value: string | number | undefined): string | undefined {
|
|
256
|
+
if (value === undefined) return undefined
|
|
257
|
+
return typeof value === 'number' ? `${value}px` : value
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function columnStyle(col: DataFrameColumn) {
|
|
261
|
+
return {
|
|
262
|
+
width: toCssSize(activeWidths.value[col.key] ?? col.width),
|
|
263
|
+
minWidth: toCssSize(col.minWidth),
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let resizeState: { key: string; startX: number; startWidth: number; min: number } | null = null
|
|
268
|
+
|
|
269
|
+
function startResize(col: DataFrameColumn, event: MouseEvent) {
|
|
270
|
+
event.preventDefault()
|
|
271
|
+
event.stopPropagation()
|
|
272
|
+
const th = (event.target as HTMLElement).closest('th')
|
|
273
|
+
const startWidth =
|
|
274
|
+
th instanceof HTMLElement ? th.offsetWidth : (typeof col.width === 'number' ? col.width : MIN_COL_WIDTH)
|
|
275
|
+
const min = Math.max(MIN_COL_WIDTH, typeof col.minWidth === 'number' ? col.minWidth : 0)
|
|
276
|
+
resizeState = { key: col.key, startX: event.clientX, startWidth, min }
|
|
277
|
+
window.addEventListener('mousemove', onResizeMove)
|
|
278
|
+
window.addEventListener('mouseup', stopResize)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function onResizeMove(event: MouseEvent) {
|
|
282
|
+
if (!resizeState) return
|
|
283
|
+
const next = Math.max(resizeState.min, resizeState.startWidth + (event.clientX - resizeState.startX))
|
|
284
|
+
const widths = { ...activeWidths.value, [resizeState.key]: next }
|
|
285
|
+
if (props.columnWidths === undefined) internalWidths.value = widths
|
|
286
|
+
emit('update:columnWidths', widths)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function stopResize() {
|
|
290
|
+
resizeState = null
|
|
291
|
+
window.removeEventListener('mousemove', onResizeMove)
|
|
292
|
+
window.removeEventListener('mouseup', stopResize)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
onBeforeUnmount(stopResize)
|
|
296
|
+
|
|
297
|
+
function handleDeleteRow(row: Record<string, unknown>, index: number) {
|
|
298
|
+
emit('delete-row', row, index)
|
|
299
|
+
}
|
|
300
|
+
|
|
237
301
|
const wrapperStyle = computed(() => {
|
|
238
302
|
if (!props.maxHeight) return undefined
|
|
239
303
|
const maxHeight = typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight
|
|
@@ -287,12 +351,10 @@ const wrapperStyle = computed(() => {
|
|
|
287
351
|
{
|
|
288
352
|
'mint-dataframe__th--sortable': isColumnSortable(col),
|
|
289
353
|
'mint-dataframe__th--sorted': activeSort?.key === col.key && activeSort?.direction,
|
|
354
|
+
'mint-dataframe__th--resizable': resizable,
|
|
290
355
|
},
|
|
291
356
|
]"
|
|
292
|
-
:style="
|
|
293
|
-
width: col.width ? (typeof col.width === 'number' ? `${col.width}px` : col.width) : undefined,
|
|
294
|
-
minWidth: col.minWidth ? (typeof col.minWidth === 'number' ? `${col.minWidth}px` : col.minWidth) : undefined,
|
|
295
|
-
}"
|
|
357
|
+
:style="columnStyle(col)"
|
|
296
358
|
@click="isColumnSortable(col) ? handleSort(col) : undefined"
|
|
297
359
|
>
|
|
298
360
|
<slot :name="`header-${col.key}`" :column="col">
|
|
@@ -318,7 +380,21 @@ const wrapperStyle = computed(() => {
|
|
|
318
380
|
</svg>
|
|
319
381
|
</div>
|
|
320
382
|
</slot>
|
|
383
|
+
<span
|
|
384
|
+
v-if="resizable"
|
|
385
|
+
class="mint-dataframe__resize-handle"
|
|
386
|
+
role="separator"
|
|
387
|
+
aria-orientation="vertical"
|
|
388
|
+
aria-label="Resize column"
|
|
389
|
+
@mousedown="startResize(col, $event)"
|
|
390
|
+
@click.stop
|
|
391
|
+
/>
|
|
321
392
|
</th>
|
|
393
|
+
<th
|
|
394
|
+
v-if="deletable"
|
|
395
|
+
:class="['mint-dataframe__th', `mint-dataframe__th--${size}`, 'mint-dataframe__th--actions']"
|
|
396
|
+
aria-label="Actions"
|
|
397
|
+
/>
|
|
322
398
|
</tr>
|
|
323
399
|
</thead>
|
|
324
400
|
|
|
@@ -363,6 +439,22 @@ const wrapperStyle = computed(() => {
|
|
|
363
439
|
{{ formatCell(col, row, rowIndex) }}
|
|
364
440
|
</slot>
|
|
365
441
|
</td>
|
|
442
|
+
<td
|
|
443
|
+
v-if="deletable"
|
|
444
|
+
:class="['mint-dataframe__td', `mint-dataframe__td--${size}`, 'mint-dataframe__td--actions']"
|
|
445
|
+
>
|
|
446
|
+
<button
|
|
447
|
+
type="button"
|
|
448
|
+
class="mint-dataframe__delete-btn"
|
|
449
|
+
:aria-label="deleteLabel"
|
|
450
|
+
:title="deleteLabel"
|
|
451
|
+
@click.stop="handleDeleteRow(row, rowIndex)"
|
|
452
|
+
>
|
|
453
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
454
|
+
<path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" /><path d="M10 11v6" /><path d="M14 11v6" />
|
|
455
|
+
</svg>
|
|
456
|
+
</button>
|
|
457
|
+
</td>
|
|
366
458
|
</tr>
|
|
367
459
|
</tbody>
|
|
368
460
|
</table>
|
|
@@ -473,3 +473,82 @@ html.dark .mint-dataframe__loading {
|
|
|
473
473
|
width: 0.875rem;
|
|
474
474
|
height: 0.875rem;
|
|
475
475
|
}
|
|
476
|
+
|
|
477
|
+
/* Resizable columns */
|
|
478
|
+
.mint-dataframe__th--resizable {
|
|
479
|
+
position: relative;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
.mint-dataframe__resize-handle {
|
|
483
|
+
position: absolute;
|
|
484
|
+
top: 0;
|
|
485
|
+
right: 0;
|
|
486
|
+
width: 7px;
|
|
487
|
+
height: 100%;
|
|
488
|
+
cursor: col-resize;
|
|
489
|
+
user-select: none;
|
|
490
|
+
touch-action: none;
|
|
491
|
+
z-index: 1;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.mint-dataframe__resize-handle::after {
|
|
495
|
+
content: '';
|
|
496
|
+
position: absolute;
|
|
497
|
+
top: 20%;
|
|
498
|
+
right: 3px;
|
|
499
|
+
width: 1px;
|
|
500
|
+
height: 60%;
|
|
501
|
+
background-color: var(--border-color);
|
|
502
|
+
transition: background-color 0.15s ease;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.mint-dataframe__resize-handle:hover::after {
|
|
506
|
+
width: 2px;
|
|
507
|
+
background-color: var(--color-primary);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/* Row delete action column */
|
|
511
|
+
.mint-dataframe__th--actions,
|
|
512
|
+
.mint-dataframe__td--actions {
|
|
513
|
+
width: 2.75rem;
|
|
514
|
+
text-align: center !important;
|
|
515
|
+
white-space: nowrap;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.mint-dataframe__delete-btn {
|
|
519
|
+
display: inline-flex;
|
|
520
|
+
align-items: center;
|
|
521
|
+
justify-content: center;
|
|
522
|
+
width: 1.5rem;
|
|
523
|
+
height: 1.5rem;
|
|
524
|
+
border: 0;
|
|
525
|
+
border-radius: var(--radius-md);
|
|
526
|
+
background: transparent;
|
|
527
|
+
color: var(--text-muted);
|
|
528
|
+
cursor: pointer;
|
|
529
|
+
opacity: 0;
|
|
530
|
+
transition:
|
|
531
|
+
background-color 0.15s ease,
|
|
532
|
+
color 0.15s ease,
|
|
533
|
+
opacity 0.15s ease;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.mint-dataframe__row:hover .mint-dataframe__delete-btn,
|
|
537
|
+
.mint-dataframe__delete-btn:focus-visible {
|
|
538
|
+
opacity: 1;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.mint-dataframe__delete-btn:hover {
|
|
542
|
+
background-color: color-mix(in srgb, var(--color-error, #ef4444) 14%, transparent);
|
|
543
|
+
color: var(--color-error, #ef4444);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.mint-dataframe__delete-btn:focus-visible {
|
|
547
|
+
outline: none;
|
|
548
|
+
box-shadow: var(--focus-ring);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.mint-dataframe__delete-btn svg {
|
|
552
|
+
width: 0.875rem;
|
|
553
|
+
height: 0.875rem;
|
|
554
|
+
}
|