@serhiitupilow/nuxt-table 0.1.7 → 1.0.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/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serhiitupilow/nuxt-table",
3
3
  "configKey": "nuxtTable",
4
- "version": "0.1.7",
4
+ "version": "1.0.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -1,5 +1,11 @@
1
1
  <script setup>
2
- import { computed, getCurrentInstance, toRef } from "vue";
2
+ import {
3
+ computed,
4
+ getCurrentInstance,
5
+ onBeforeUpdate,
6
+ shallowRef,
7
+ toRef
8
+ } from "vue";
3
9
  import { useNuxtTable } from "../composables/useNuxtTable";
4
10
  const props = defineProps({
5
11
  columns: { type: Array, required: true },
@@ -15,13 +21,17 @@ const props = defineProps({
15
21
  });
16
22
  const emit = defineEmits(["columnOrderChange", "manualSortChange", "manualFilterChange"]);
17
23
  const instance = getCurrentInstance();
24
+ const vnodeProps = shallowRef(
25
+ instance?.vnode.props ?? null
26
+ );
27
+ onBeforeUpdate(() => {
28
+ vnodeProps.value = instance?.vnode.props ?? null;
29
+ });
18
30
  const hasManualSortChangeListener = computed(() => {
19
- const vnodeProps = instance?.vnode.props;
20
- return Boolean(vnodeProps?.onManualSortChange);
31
+ return Boolean(vnodeProps.value?.onManualSortChange);
21
32
  });
22
33
  const hasManualFilterChangeListener = computed(() => {
23
- const vnodeProps = instance?.vnode.props;
24
- return Boolean(vnodeProps?.onManualFilterChange);
34
+ return Boolean(vnodeProps.value?.onManualFilterChange);
25
35
  });
26
36
  const defaultClassNames = {
27
37
  root: "nuxt-table",
@@ -80,15 +90,17 @@ const {
80
90
  storageKey: toRef(props, "storageKey"),
81
91
  rowKey: toRef(props, "rowKey"),
82
92
  enableColumnDnd: toRef(props, "enableColumnDnd"),
93
+ isManualSortMode: hasManualSortChangeListener,
94
+ isManualFilterMode: hasManualFilterChangeListener,
83
95
  onColumnOrderChange: (payload) => {
84
96
  emit("columnOrderChange", payload);
85
97
  },
86
- onManualSortChange: hasManualSortChangeListener.value ? (payload) => {
98
+ onManualSortChange: (payload) => {
87
99
  emit("manualSortChange", payload);
88
- } : void 0,
89
- onManualFilterChange: hasManualFilterChangeListener.value ? (payload) => {
100
+ },
101
+ onManualFilterChange: (payload) => {
90
102
  emit("manualFilterChange", payload);
91
- } : void 0
103
+ }
92
104
  });
93
105
  const displayedColumns = computed(() => {
94
106
  if (!props.enabledColumns) {
@@ -97,6 +109,12 @@ const displayedColumns = computed(() => {
97
109
  const enabledKeySet = new Set(props.enabledColumns);
98
110
  return orderedColumns.value.filter((column) => enabledKeySet.has(column.key));
99
111
  });
112
+ const keyedRows = computed(() => {
113
+ return sortedRows.value.map((row, rowIndex) => ({
114
+ row,
115
+ key: resolveRowKey(row, rowIndex)
116
+ }));
117
+ });
100
118
  </script>
101
119
 
102
120
  <template>
@@ -131,22 +149,22 @@ const displayedColumns = computed(() => {
131
149
  </thead>
132
150
  <tbody :class="mergedClassNames.tableBody">
133
151
  <tr
134
- v-for="(row, rowIndex) in sortedRows"
135
- :key="resolveRowKey(row, rowIndex)"
152
+ v-for="rowEntry in keyedRows"
153
+ :key="rowEntry.key"
136
154
  :class="mergedClassNames.bodyRow"
137
155
  >
138
156
  <NuxtTableBodyCell
139
157
  v-for="column in displayedColumns"
140
- :key="`${resolveRowKey(row, rowIndex)}-${column.key}`"
141
- :row="row"
142
- :row-key="resolveRowKey(row, rowIndex)"
158
+ :key="`${rowEntry.key}-${column.key}`"
159
+ :row="rowEntry.row"
160
+ :row-key="rowEntry.key"
143
161
  :column="column"
144
- :value="resolveDisplayValue(row, column)"
162
+ :value="resolveDisplayValue(rowEntry.row, column)"
145
163
  :column-style="getColumnStyle(column.key)"
146
164
  :class-names="mergedClassNames"
147
165
  />
148
166
  </tr>
149
- <tr v-if="sortedRows.length === 0">
167
+ <tr v-if="keyedRows.length === 0">
150
168
  <td
151
169
  :colspan="Math.max(displayedColumns.length, 1)"
152
170
  :class="mergedClassNames.emptyCell"
@@ -17,13 +17,7 @@ export declare function useNuxtTable(options: UseNuxtTableOptions): {
17
17
  onHeaderDragLeave: (columnKey: string) => void;
18
18
  onHeaderDrop: (targetColumnKey: string) => Promise<void>;
19
19
  onHeaderDragEnd: () => void;
20
- getColumnStyle: (columnKey: string) => {
21
- width?: undefined;
22
- minWidth?: undefined;
23
- } | {
24
- width: string;
25
- minWidth: string;
26
- };
20
+ getColumnStyle: (columnKey: string) => Record<string, string>;
27
21
  startColumnResize: (event: MouseEvent, columnKey: string) => void;
28
22
  setHeaderElement: (columnKey: string, element: Element | ComponentPublicInstance | null) => void;
29
23
  resolveDisplayValue: (row: TableRow, column: NuxtTableColumn) => any;
@@ -7,6 +7,7 @@ import {
7
7
  watch
8
8
  } from "vue";
9
9
  const MIN_COLUMN_WIDTH = 140;
10
+ const EMPTY_STYLE = {};
10
11
  export function useNuxtTable(options) {
11
12
  const columnOrder = ref([]);
12
13
  const enabledColumnKeys = ref([]);
@@ -19,11 +20,20 @@ export function useNuxtTable(options) {
19
20
  const hasLoadedPersistence = ref(false);
20
21
  const headerElements = ref({});
21
22
  const columnWidths = ref({});
23
+ const resolverPathCache = /* @__PURE__ */ new Map();
22
24
  const activeResize = ref(null);
23
- const isManualSortMode = computed(() => Boolean(options.onManualSortChange));
24
- const isManualFilterMode = computed(
25
- () => Boolean(options.onManualFilterChange)
26
- );
25
+ const isManualSortMode = computed(() => {
26
+ if (options.isManualSortMode) {
27
+ return options.isManualSortMode.value;
28
+ }
29
+ return Boolean(options.onManualSortChange);
30
+ });
31
+ const isManualFilterMode = computed(() => {
32
+ if (options.isManualFilterMode) {
33
+ return options.isManualFilterMode.value;
34
+ }
35
+ return Boolean(options.onManualFilterChange);
36
+ });
27
37
  const availableColumnKeys = computed(
28
38
  () => options.columns.value.map((column) => column.key)
29
39
  );
@@ -34,30 +44,60 @@ export function useNuxtTable(options) {
34
44
  return columnOrder.value.map((columnKey) => columnsByKey.value.get(columnKey)).filter((column) => Boolean(column));
35
45
  });
36
46
  const visibleColumns = computed(() => {
47
+ const enabledKeySet = new Set(enabledColumnKeys.value);
37
48
  return orderedColumns.value.filter(
38
- (column) => enabledColumnKeys.value.includes(column.key)
49
+ (column) => enabledKeySet.has(column.key)
39
50
  );
40
51
  });
52
+ const activeTextFilters = computed(() => {
53
+ if (isManualFilterMode.value) {
54
+ return [];
55
+ }
56
+ const nextFilters = [];
57
+ for (const column of orderedColumns.value) {
58
+ const filterValue = filters.value[column.key];
59
+ if (!isFilterActive(filterValue)) {
60
+ continue;
61
+ }
62
+ nextFilters.push({
63
+ key: column.key,
64
+ resolver: column.filterKey ?? column.key,
65
+ filterText: String(filterValue ?? "").toLowerCase()
66
+ });
67
+ }
68
+ return nextFilters;
69
+ });
41
70
  const filteredRows = computed(() => {
71
+ if (isManualFilterMode.value) {
72
+ return options.rows.value;
73
+ }
74
+ const activeFilters = activeTextFilters.value;
75
+ if (activeFilters.length === 0) {
76
+ return options.rows.value;
77
+ }
42
78
  return options.rows.value.filter((row) => {
43
- return orderedColumns.value.every((column) => {
44
- const filterValue = filters.value[column.key];
45
- if (!isFilterActive(filterValue)) {
46
- return true;
47
- }
48
- if (isManualFilterMode.value) {
49
- return true;
50
- }
51
- const candidate = resolveColumnValue(
52
- row,
53
- column.filterKey ?? column.key
54
- );
79
+ return activeFilters.every(({ resolver, filterText }) => {
80
+ const candidate = resolveColumnValue(row, resolver);
55
81
  const candidateText = String(candidate ?? "").toLowerCase();
56
- const filterText = String(filterValue ?? "").toLowerCase();
57
82
  return candidateText.includes(filterText);
58
83
  });
59
84
  });
60
85
  });
86
+ const columnStylesByKey = computed(() => {
87
+ const styles = {};
88
+ for (const [columnKey, width] of Object.entries(columnWidths.value)) {
89
+ if (!width) {
90
+ continue;
91
+ }
92
+ const safeWidth = Math.max(MIN_COLUMN_WIDTH, width);
93
+ const widthPx = `${safeWidth}px`;
94
+ styles[columnKey] = {
95
+ width: widthPx,
96
+ minWidth: widthPx
97
+ };
98
+ }
99
+ return styles;
100
+ });
61
101
  const sortedRows = computed(() => {
62
102
  if (!sortState.value) {
63
103
  return filteredRows.value;
@@ -83,13 +123,9 @@ export function useNuxtTable(options) {
83
123
  loadPersistedState();
84
124
  hasLoadedPersistence.value = true;
85
125
  });
86
- watch(
87
- () => options.columns.value,
88
- () => {
89
- initializeColumnState();
90
- },
91
- { deep: true }
92
- );
126
+ watch(availableColumnKeys, () => {
127
+ initializeColumnState();
128
+ });
93
129
  watch(
94
130
  [columnOrder, enabledColumnKeys, columnWidths],
95
131
  () => {
@@ -123,7 +159,8 @@ export function useNuxtTable(options) {
123
159
  const keptKeys = columnOrder.value.filter(
124
160
  (key) => currentKeySet.has(key)
125
161
  );
126
- const newKeys = currentKeys.filter((key) => !keptKeys.includes(key));
162
+ const keptKeySet = new Set(keptKeys);
163
+ const newKeys = currentKeys.filter((key) => !keptKeySet.has(key));
127
164
  columnOrder.value = [...keptKeys, ...newKeys];
128
165
  }
129
166
  if (!enabledColumnKeys.value.length) {
@@ -133,8 +170,9 @@ export function useNuxtTable(options) {
133
170
  const keptEnabledKeys = enabledColumnKeys.value.filter(
134
171
  (key) => currentKeySet.has(key)
135
172
  );
173
+ const keptEnabledKeySet = new Set(keptEnabledKeys);
136
174
  const missingEnabledKeys = currentKeys.filter(
137
- (key) => !keptEnabledKeys.includes(key)
175
+ (key) => !keptEnabledKeySet.has(key)
138
176
  );
139
177
  enabledColumnKeys.value = [...keptEnabledKeys, ...missingEnabledKeys];
140
178
  }
@@ -157,17 +195,19 @@ export function useNuxtTable(options) {
157
195
  return;
158
196
  }
159
197
  try {
198
+ const availableKeySet = new Set(availableColumnKeys.value);
160
199
  const persistedOrder = localStorage.getItem(buildStorageKey("order"));
161
200
  if (persistedOrder) {
162
201
  const parsedOrder = JSON.parse(persistedOrder);
163
202
  if (Array.isArray(parsedOrder)) {
164
203
  const validPersistedOrder = parsedOrder.filter(
165
204
  (key) => {
166
- return typeof key === "string" && availableColumnKeys.value.includes(key);
205
+ return typeof key === "string" && availableKeySet.has(key);
167
206
  }
168
207
  );
208
+ const validPersistedOrderSet = new Set(validPersistedOrder);
169
209
  const missingKeys = availableColumnKeys.value.filter(
170
- (key) => !validPersistedOrder.includes(key)
210
+ (key) => !validPersistedOrderSet.has(key)
171
211
  );
172
212
  columnOrder.value = [...validPersistedOrder, ...missingKeys];
173
213
  }
@@ -180,7 +220,7 @@ export function useNuxtTable(options) {
180
220
  if (Array.isArray(parsedEnabledColumns)) {
181
221
  enabledColumnKeys.value = parsedEnabledColumns.filter(
182
222
  (key) => {
183
- return typeof key === "string" && availableColumnKeys.value.includes(key);
223
+ return typeof key === "string" && availableKeySet.has(key);
184
224
  }
185
225
  );
186
226
  }
@@ -273,7 +313,8 @@ export function useNuxtTable(options) {
273
313
  });
274
314
  }
275
315
  function toggleColumn(columnKey) {
276
- if (enabledColumnKeys.value.includes(columnKey)) {
316
+ const enabledKeySet = new Set(enabledColumnKeys.value);
317
+ if (enabledKeySet.has(columnKey)) {
277
318
  if (enabledColumnKeys.value.length === 1) {
278
319
  return;
279
320
  }
@@ -349,15 +390,7 @@ export function useNuxtTable(options) {
349
390
  dragOverColumnKey.value = null;
350
391
  }
351
392
  function getColumnStyle(columnKey) {
352
- const width = columnWidths.value[columnKey];
353
- if (!width) {
354
- return {};
355
- }
356
- const safeWidth = Math.max(MIN_COLUMN_WIDTH, width);
357
- return {
358
- width: `${safeWidth}px`,
359
- minWidth: `${safeWidth}px`
360
- };
393
+ return columnStylesByKey.value[columnKey] ?? EMPTY_STYLE;
361
394
  }
362
395
  function startColumnResize(event, columnKey) {
363
396
  if (!import.meta.client) {
@@ -381,10 +414,7 @@ export function useNuxtTable(options) {
381
414
  MIN_COLUMN_WIDTH,
382
415
  Math.round(activeResize.value.startWidth + delta)
383
416
  );
384
- columnWidths.value = {
385
- ...columnWidths.value,
386
- [activeResize.value.columnKey]: nextWidth
387
- };
417
+ columnWidths.value[activeResize.value.columnKey] = nextWidth;
388
418
  }
389
419
  function onColumnResizeEnd() {
390
420
  stopResizing();
@@ -453,7 +483,11 @@ export function useNuxtTable(options) {
453
483
  if (typeof resolver === "function") {
454
484
  return resolver(row);
455
485
  }
456
- const pathParts = resolver.split(".");
486
+ const cachedPath = resolverPathCache.get(resolver);
487
+ const pathParts = cachedPath ?? resolver.split(".");
488
+ if (!cachedPath) {
489
+ resolverPathCache.set(resolver, pathParts);
490
+ }
457
491
  let currentValue = row;
458
492
  for (const pathPart of pathParts) {
459
493
  if (currentValue == null) {
@@ -68,6 +68,8 @@ export interface UseNuxtTableOptions {
68
68
  storageKey: Ref<string>;
69
69
  rowKey: Ref<string | ((row: TableRow, index: number) => string | number)>;
70
70
  enableColumnDnd: Ref<boolean>;
71
+ isManualSortMode?: Ref<boolean>;
72
+ isManualFilterMode?: Ref<boolean>;
71
73
  onColumnOrderChange?: (payload: NuxtTableColumnOrderChange) => void;
72
74
  onManualSortChange?: (payload: NuxtTableManualSortChange) => void;
73
75
  onManualFilterChange?: (payload: NuxtTableManualFilterChange) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serhiitupilow/nuxt-table",
3
- "version": "0.1.7",
3
+ "version": "1.0.0",
4
4
  "description": "Nuxt module with a functional table component (sorting, filtering, column visibility, resize, optional DnD)",
5
5
  "type": "module",
6
6
  "license": "MIT",