@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/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-DjfatNJv.js";
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
@@ -1,4 +1,4 @@
1
- import { t as components_exports } from "./components-DjfatNJv.js";
1
+ import { t as components_exports } from "./components-C7UFkNIp.js";
2
2
  import "./BaseButton-Dgqrze41.js";
3
3
  import "./BaseSelect-ekgr9fDo.js";
4
4
  import "./useControlSchema-BZNdalmL.js";
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.45",
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
+ }