tablevu 0.3.0-beta.5 → 0.3.0

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/tablevu.cjs CHANGED
@@ -107,6 +107,8 @@ const tablevu = vue.defineComponent({
107
107
  let groups = [];
108
108
  let adapter = null;
109
109
  let searchTimer = null;
110
+ let draggedColumnName = null;
111
+ let resizeCleanup = null;
110
112
  const state = vue.reactive({
111
113
  caption: null,
112
114
  group: {
@@ -121,8 +123,12 @@ const tablevu = vue.defineComponent({
121
123
  current: 0,
122
124
  type: null
123
125
  },
124
- sort: (props.settings && props.settings.sort) ? props.settings.sort : [],
125
- columns: (props.settings && props.settings.columns) ? props.settings.columns : [],
126
+ sort: (props.settings?.sort ?? []).map(sort => ({ ...sort })),
127
+ columns: (props.settings?.columns ?? []).map(column => ({
128
+ ...column,
129
+ style: column.style ? { ...column.style } : undefined,
130
+ depth: column.depth ? [...column.depth] : undefined
131
+ })),
126
132
  footer: {
127
133
  button: {
128
134
  class: (props.settings && props.settings.footer && props.settings.footer.button && props.settings.footer.button.class) || 'btn'
@@ -163,7 +169,62 @@ const tablevu = vue.defineComponent({
163
169
  vue.onBeforeUnmount(() => {
164
170
  if (searchTimer)
165
171
  clearTimeout(searchTimer);
172
+ resizeCleanup?.();
166
173
  });
174
+ function remapSortColumns(previousNames) {
175
+ state.sort.forEach((sort, index) => {
176
+ const name = previousNames[index] ?? sort.name;
177
+ if (!name)
178
+ return;
179
+ const columnIndex = state.columns.findIndex(column => column.name === name);
180
+ if (columnIndex >= 0)
181
+ sort.index = columnIndex;
182
+ else {
183
+ sort.name = name;
184
+ delete sort.index;
185
+ }
186
+ });
187
+ }
188
+ function reorderColumn(sourceName, targetName) {
189
+ const sourceIndex = state.columns.findIndex(column => column.name === sourceName);
190
+ const targetIndex = state.columns.findIndex(column => column.name === targetName);
191
+ if (sourceIndex < 0 || targetIndex < 0 || sourceIndex === targetIndex)
192
+ return;
193
+ const sortNames = state.sort.map(sort => sort.index !== undefined
194
+ ? state.columns[sort.index]?.name
195
+ : sort.name);
196
+ const [column] = state.columns.splice(sourceIndex, 1);
197
+ state.columns.splice(targetIndex, 0, column);
198
+ remapSortColumns(sortNames);
199
+ }
200
+ function hideColumn(name) {
201
+ const sortNames = state.sort.map(sort => sort.index !== undefined
202
+ ? state.columns[sort.index]?.name
203
+ : sort.name);
204
+ const index = state.columns.findIndex(column => column.name === name);
205
+ if (index < 0)
206
+ return;
207
+ state.columns.splice(index, 1);
208
+ remapSortColumns(sortNames);
209
+ }
210
+ function startColumnResize(event, column, header) {
211
+ event.preventDefault();
212
+ event.stopPropagation();
213
+ resizeCleanup?.();
214
+ const startX = event.clientX;
215
+ const startWidth = header.getBoundingClientRect().width;
216
+ const move = (moveEvent) => {
217
+ column.width = `${Math.max(40, startWidth + moveEvent.clientX - startX)}px`;
218
+ };
219
+ const up = () => {
220
+ document.removeEventListener('pointermove', move);
221
+ document.removeEventListener('pointerup', up);
222
+ resizeCleanup = null;
223
+ };
224
+ resizeCleanup = up;
225
+ document.addEventListener('pointermove', move);
226
+ document.addEventListener('pointerup', up);
227
+ }
167
228
  if (props.filter)
168
229
  state.filter = props.filter;
169
230
  vue.watch(() => props.filter, () => {
@@ -456,11 +517,12 @@ const tablevu = vue.defineComponent({
456
517
  else
457
518
  labelContent.push(vue.h('span', { style: { float: 'right' } }, '▲'));
458
519
  }
520
+ const column = state.columns[i];
459
521
  const headerStyle = { position: 'relative' };
460
- if (state.columns[i].width) {
461
- headerStyle.width = state.columns[i].width;
462
- headerStyle.minWidth = state.columns[i].width;
463
- headerStyle.maxWidth = state.columns[i].width;
522
+ if (column.width) {
523
+ headerStyle.width = column.width;
524
+ headerStyle.minWidth = column.width;
525
+ headerStyle.maxWidth = column.width;
464
526
  }
465
527
  let headerLink;
466
528
  let popupContent;
@@ -489,7 +551,47 @@ const tablevu = vue.defineComponent({
489
551
  else
490
552
  headerLink = vue.h('a', { href: '#', onClick: (event) => { sortBy(i); event.preventDefault(); }, style: { display: 'block', 'font-weight': 500, textDecoration: 'none', textAlign: 'center' } }, labelContent);
491
553
  if (state.columns[i].filter) ;
492
- headers.push(vue.h('th', { colspan: null, style: headerStyle }, [headerLink, popupContent]));
554
+ const resizeHandle = column.resizable === false ? null : vue.h('div', {
555
+ 'aria-hidden': 'true',
556
+ 'onPointerdown': (event) => startColumnResize(event, column, event.currentTarget.parentElement),
557
+ style: { position: 'absolute', top: 0, right: '-3px', width: '6px', height: '100%', cursor: 'col-resize', touchAction: 'none', zIndex: 2 }
558
+ });
559
+ headers.push(vue.h('th', {
560
+ colspan: null,
561
+ style: headerStyle,
562
+ draggable: column.draggable !== false,
563
+ onDragstart: (event) => {
564
+ if (event.target.closest('[aria-hidden="true"]')) {
565
+ event.preventDefault();
566
+ return;
567
+ }
568
+ draggedColumnName = column.name;
569
+ if (event.dataTransfer) {
570
+ event.dataTransfer.effectAllowed = 'move';
571
+ event.dataTransfer.setData('text/plain', column.name);
572
+ }
573
+ },
574
+ onDragover: (event) => {
575
+ if (draggedColumnName) {
576
+ event.preventDefault();
577
+ if (event.dataTransfer)
578
+ event.dataTransfer.dropEffect = 'move';
579
+ }
580
+ },
581
+ onDrop: (event) => {
582
+ if (!draggedColumnName)
583
+ return;
584
+ event.preventDefault();
585
+ reorderColumn(draggedColumnName, column.name);
586
+ },
587
+ onDragend: (event) => {
588
+ const name = draggedColumnName;
589
+ draggedColumnName = null;
590
+ const rect = event.currentTarget.closest('table')?.getBoundingClientRect();
591
+ if (name && rect && (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom))
592
+ hideColumn(name);
593
+ }
594
+ }, [headerLink, popupContent, resizeHandle]));
493
595
  }
494
596
  return [vue.h('tr', {}, headers)];
495
597
  }
@@ -105,6 +105,8 @@ const tablevu = defineComponent({
105
105
  let groups = [];
106
106
  let adapter = null;
107
107
  let searchTimer = null;
108
+ let draggedColumnName = null;
109
+ let resizeCleanup = null;
108
110
  const state = reactive({
109
111
  caption: null,
110
112
  group: {
@@ -119,8 +121,12 @@ const tablevu = defineComponent({
119
121
  current: 0,
120
122
  type: null
121
123
  },
122
- sort: (props.settings && props.settings.sort) ? props.settings.sort : [],
123
- columns: (props.settings && props.settings.columns) ? props.settings.columns : [],
124
+ sort: (props.settings?.sort ?? []).map(sort => ({ ...sort })),
125
+ columns: (props.settings?.columns ?? []).map(column => ({
126
+ ...column,
127
+ style: column.style ? { ...column.style } : undefined,
128
+ depth: column.depth ? [...column.depth] : undefined
129
+ })),
124
130
  footer: {
125
131
  button: {
126
132
  class: (props.settings && props.settings.footer && props.settings.footer.button && props.settings.footer.button.class) || 'btn'
@@ -161,7 +167,62 @@ const tablevu = defineComponent({
161
167
  onBeforeUnmount(() => {
162
168
  if (searchTimer)
163
169
  clearTimeout(searchTimer);
170
+ resizeCleanup?.();
164
171
  });
172
+ function remapSortColumns(previousNames) {
173
+ state.sort.forEach((sort, index) => {
174
+ const name = previousNames[index] ?? sort.name;
175
+ if (!name)
176
+ return;
177
+ const columnIndex = state.columns.findIndex(column => column.name === name);
178
+ if (columnIndex >= 0)
179
+ sort.index = columnIndex;
180
+ else {
181
+ sort.name = name;
182
+ delete sort.index;
183
+ }
184
+ });
185
+ }
186
+ function reorderColumn(sourceName, targetName) {
187
+ const sourceIndex = state.columns.findIndex(column => column.name === sourceName);
188
+ const targetIndex = state.columns.findIndex(column => column.name === targetName);
189
+ if (sourceIndex < 0 || targetIndex < 0 || sourceIndex === targetIndex)
190
+ return;
191
+ const sortNames = state.sort.map(sort => sort.index !== undefined
192
+ ? state.columns[sort.index]?.name
193
+ : sort.name);
194
+ const [column] = state.columns.splice(sourceIndex, 1);
195
+ state.columns.splice(targetIndex, 0, column);
196
+ remapSortColumns(sortNames);
197
+ }
198
+ function hideColumn(name) {
199
+ const sortNames = state.sort.map(sort => sort.index !== undefined
200
+ ? state.columns[sort.index]?.name
201
+ : sort.name);
202
+ const index = state.columns.findIndex(column => column.name === name);
203
+ if (index < 0)
204
+ return;
205
+ state.columns.splice(index, 1);
206
+ remapSortColumns(sortNames);
207
+ }
208
+ function startColumnResize(event, column, header) {
209
+ event.preventDefault();
210
+ event.stopPropagation();
211
+ resizeCleanup?.();
212
+ const startX = event.clientX;
213
+ const startWidth = header.getBoundingClientRect().width;
214
+ const move = (moveEvent) => {
215
+ column.width = `${Math.max(40, startWidth + moveEvent.clientX - startX)}px`;
216
+ };
217
+ const up = () => {
218
+ document.removeEventListener('pointermove', move);
219
+ document.removeEventListener('pointerup', up);
220
+ resizeCleanup = null;
221
+ };
222
+ resizeCleanup = up;
223
+ document.addEventListener('pointermove', move);
224
+ document.addEventListener('pointerup', up);
225
+ }
165
226
  if (props.filter)
166
227
  state.filter = props.filter;
167
228
  watch(() => props.filter, () => {
@@ -454,11 +515,12 @@ const tablevu = defineComponent({
454
515
  else
455
516
  labelContent.push(h('span', { style: { float: 'right' } }, '▲'));
456
517
  }
518
+ const column = state.columns[i];
457
519
  const headerStyle = { position: 'relative' };
458
- if (state.columns[i].width) {
459
- headerStyle.width = state.columns[i].width;
460
- headerStyle.minWidth = state.columns[i].width;
461
- headerStyle.maxWidth = state.columns[i].width;
520
+ if (column.width) {
521
+ headerStyle.width = column.width;
522
+ headerStyle.minWidth = column.width;
523
+ headerStyle.maxWidth = column.width;
462
524
  }
463
525
  let headerLink;
464
526
  let popupContent;
@@ -487,7 +549,47 @@ const tablevu = defineComponent({
487
549
  else
488
550
  headerLink = h('a', { href: '#', onClick: (event) => { sortBy(i); event.preventDefault(); }, style: { display: 'block', 'font-weight': 500, textDecoration: 'none', textAlign: 'center' } }, labelContent);
489
551
  if (state.columns[i].filter) ;
490
- headers.push(h('th', { colspan: null, style: headerStyle }, [headerLink, popupContent]));
552
+ const resizeHandle = column.resizable === false ? null : h('div', {
553
+ 'aria-hidden': 'true',
554
+ 'onPointerdown': (event) => startColumnResize(event, column, event.currentTarget.parentElement),
555
+ style: { position: 'absolute', top: 0, right: '-3px', width: '6px', height: '100%', cursor: 'col-resize', touchAction: 'none', zIndex: 2 }
556
+ });
557
+ headers.push(h('th', {
558
+ colspan: null,
559
+ style: headerStyle,
560
+ draggable: column.draggable !== false,
561
+ onDragstart: (event) => {
562
+ if (event.target.closest('[aria-hidden="true"]')) {
563
+ event.preventDefault();
564
+ return;
565
+ }
566
+ draggedColumnName = column.name;
567
+ if (event.dataTransfer) {
568
+ event.dataTransfer.effectAllowed = 'move';
569
+ event.dataTransfer.setData('text/plain', column.name);
570
+ }
571
+ },
572
+ onDragover: (event) => {
573
+ if (draggedColumnName) {
574
+ event.preventDefault();
575
+ if (event.dataTransfer)
576
+ event.dataTransfer.dropEffect = 'move';
577
+ }
578
+ },
579
+ onDrop: (event) => {
580
+ if (!draggedColumnName)
581
+ return;
582
+ event.preventDefault();
583
+ reorderColumn(draggedColumnName, column.name);
584
+ },
585
+ onDragend: (event) => {
586
+ const name = draggedColumnName;
587
+ draggedColumnName = null;
588
+ const rect = event.currentTarget.closest('table')?.getBoundingClientRect();
589
+ if (name && rect && (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom))
590
+ hideColumn(name);
591
+ }
592
+ }, [headerLink, popupContent, resizeHandle]));
491
593
  }
492
594
  return [h('tr', {}, headers)];
493
595
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tablevu",
3
- "version": "0.3.0-beta.5",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Vue 3 data table component",
6
6
  "main": "dist/tablevu.cjs",