@katlux/preset-modern 0.1.1-alpha.95 → 0.1.1-alpha.97

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": "@katlux/preset-modern",
3
3
  "configKey": "katluxTemplateModern",
4
- "version": "0.1.1-alpha.95",
4
+ "version": "0.1.1-alpha.97",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -7,22 +7,33 @@ type __VLS_Props = {
7
7
  bulkActions?: Array<IKDatatableAction>;
8
8
  selectedRows?: any[];
9
9
  selectAll?: boolean;
10
+ currentPage?: number;
11
+ pageSize?: number;
10
12
  cellSlots?: Record<string, any>;
11
13
  headerSlots?: Record<string, any>;
12
14
  columnHeaders?: Record<string, string>;
13
15
  columnWidths?: Record<string, string>;
14
16
  emptyStateText?: string;
15
17
  emptyFilterStateText?: string;
18
+ virtualScroll?: boolean;
19
+ itemHeight?: number;
20
+ viewportHeight?: number;
21
+ buffer?: number;
16
22
  };
17
23
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
18
24
  "update:selectedRows": (value: any[]) => any;
19
25
  "update:selectAll": (value: boolean) => any;
26
+ "update:currentPage": (value: number) => any;
27
+ "update:pageSize": (value: number) => any;
20
28
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
21
29
  "onUpdate:selectedRows"?: ((value: any[]) => any) | undefined;
22
30
  "onUpdate:selectAll"?: ((value: boolean) => any) | undefined;
31
+ "onUpdate:currentPage"?: ((value: number) => any) | undefined;
32
+ "onUpdate:pageSize"?: ((value: number) => any) | undefined;
23
33
  }>, {
24
34
  selectedRows: any[];
25
35
  selectAll: boolean;
36
+ buffer: number;
26
37
  bulkActions: Array<IKDatatableAction>;
27
38
  visibleFields: Array<string> | null;
28
39
  rowActions: Array<IKDatatableAction>;
@@ -32,6 +43,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
32
43
  columnWidths: Record<string, string>;
33
44
  emptyStateText: string;
34
45
  emptyFilterStateText: string;
46
+ virtualScroll: boolean;
47
+ itemHeight: number;
48
+ viewportHeight: number;
35
49
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
36
50
  declare const _default: typeof __VLS_export;
37
51
  export default _default;
@@ -1,10 +1,20 @@
1
1
  <template lang="pug">
2
- .k-datatable.modern-datatable(:style="{ '--page-size': dpPageSize || 10 }" role="region" :aria-label="t('Data Table')")
2
+ .k-datatable.modern-datatable(
3
+ :class="{ 'is-virtual-scroll': props.virtualScroll }"
4
+ :style="props.virtualScroll ? { ...containerCssVars, '--k-datatable-viewport-height': (props.viewportHeight || 500) + 'px' } : { '--page-size': effectivePageSize }"
5
+ role="region"
6
+ :aria-label="t('Data Table')"
7
+ )
3
8
  .header(v-if="props.bulkActions && props.bulkActions.length > 0 && selectedRows.length > 0")
4
9
  .selected-count {{ t('{count} items selected').replace('{count}', (selectedRows?.length || 0).toString()) }}
5
10
  KBulkActions.bulk-actions(:bulkActions="props.bulkActions" :selectedRows="selectedRows" :selectAll="selectAll")
6
11
 
7
- .k-datatable-wrapper
12
+ .k-datatable-wrapper(
13
+ ref="containerRef"
14
+ @scroll="props.virtualScroll ? onScroll($event) : undefined"
15
+ @keydown="handleKeyDown"
16
+ :tabindex="props.virtualScroll ? 0 : undefined"
17
+ )
8
18
  .loading-overlay(v-if="isLoading")
9
19
  .spinner
10
20
 
@@ -19,11 +29,21 @@
19
29
  th.actions-cell(v-if="props.rowActions && props.rowActions.length > 0") {{ t('Actions') }}
20
30
 
21
31
  tbody
22
- tr.empty-row(v-if="!isLoading && (!pageRows || pageRows.length === 0)")
23
- td.empty-state(:colspan="(visibleFields || props.dataProvider.fields || []).length + (props.bulkActions && props.bulkActions.length > 0 ? 1 : 0) + (props.rowActions && props.rowActions.length > 0 ? 1 : 0)")
32
+ tr.empty-row(v-if="!isLoading && (!datatableItems || datatableItems.length === 0)")
33
+ td.empty-state(:colspan="totalColspan")
24
34
  span(v-if="dpFilter?.active || props.dataProvider.filter?.value?.active") {{ props.emptyFilterStateText || t('No result found for filter') }}
25
35
  span(v-else) {{ props.emptyStateText || t('No result') }}
26
- tr(v-for="(row, index) in pageRows" :key="getRowKey(row, index)" :class="{ 'selected': isRowSelected(row) }")
36
+ tr.k-virtual-spacer.is-top(
37
+ v-if="props.virtualScroll && topSpacerHeight > 0"
38
+ :style="{ '--spacer-height': topSpacerHeight + 'px' }"
39
+ role="presentation"
40
+ )
41
+ td(:colspan="totalColspan")
42
+ tr(
43
+ v-for="(row, index) in displayRows"
44
+ :key="getRowKey(row, index)"
45
+ :class="{ 'selected': isRowSelected(row), 'active-focus': focusedIndex === (props.virtualScroll ? startIndex + index : index) }"
46
+ )
27
47
  td.check-cell(v-if="props.bulkActions && props.bulkActions.length > 0")
28
48
  KCheckbox(:value="row" v-model="selectedRows")
29
49
  td(v-for="visibleField in visibleFields || props.dataProvider.fields" :key="visibleField" :data-field="visibleField" :style="getColumnStyle(visibleField)")
@@ -46,16 +66,23 @@
46
66
  template(#icon v-if="rowAction.icon")
47
67
  KIcon(:iconname="rowAction.icon" :width="16" :height="16")
48
68
  template(#default v-if="!rowAction.iconOnly") {{ rowAction.label }}
69
+ tr.k-virtual-spacer.is-bottom(
70
+ v-if="props.virtualScroll && bottomSpacerHeight > 0"
71
+ :style="{ '--spacer-height': bottomSpacerHeight + 'px' }"
72
+ role="presentation"
73
+ )
74
+ td(:colspan="totalColspan")
49
75
 
50
- .footer
76
+ .footer(v-if="!props.virtualScroll")
51
77
  KPagination.pagination(:dataProvider="props.dataProvider")
52
78
  </template>
53
79
 
54
80
  <script setup>
55
- import { computed } from "vue";
81
+ import { ref, computed, isRef } from "vue";
56
82
  import { ADataProvider } from "@katlux/providers";
57
83
  import { useKatluxI18n } from "#toolkit/composables/useKatluxI18n";
58
84
  import { useDataProvider } from "#toolkit/composables/useDataProvider";
85
+ import { useVirtualScroll } from "#toolkit/composables/useVirtualScroll";
59
86
  const { t } = useKatluxI18n();
60
87
  const props = defineProps({
61
88
  dataProvider: { type: Object, required: true },
@@ -64,14 +91,20 @@ const props = defineProps({
64
91
  bulkActions: { type: Array, required: false, default: () => [] },
65
92
  selectedRows: { type: Array, required: false, default: () => [] },
66
93
  selectAll: { type: Boolean, required: false, default: false },
94
+ currentPage: { type: Number, required: false },
95
+ pageSize: { type: Number, required: false },
67
96
  cellSlots: { type: Object, required: false, default: void 0 },
68
97
  headerSlots: { type: Object, required: false, default: void 0 },
69
98
  columnHeaders: { type: Object, required: false, default: void 0 },
70
99
  columnWidths: { type: Object, required: false, default: void 0 },
71
100
  emptyStateText: { type: String, required: false, default: "" },
72
- emptyFilterStateText: { type: String, required: false, default: "" }
101
+ emptyFilterStateText: { type: String, required: false, default: "" },
102
+ virtualScroll: { type: Boolean, required: false, default: false },
103
+ itemHeight: { type: Number, required: false, default: 50 },
104
+ viewportHeight: { type: Number, required: false, default: 500 },
105
+ buffer: { type: Number, required: false, default: 5 }
73
106
  });
74
- const emit = defineEmits(["update:selectedRows", "update:selectAll"]);
107
+ const emit = defineEmits(["update:selectedRows", "update:selectAll", "update:currentPage", "update:pageSize"]);
75
108
  const selectedRows = computed({
76
109
  get: () => props.selectedRows || [],
77
110
  set: (val) => emit("update:selectedRows", val)
@@ -80,6 +113,10 @@ const selectAll = computed({
80
113
  get: () => props.selectAll || false,
81
114
  set: (val) => emit("update:selectAll", val)
82
115
  });
116
+ const effectivePageSize = computed(() => {
117
+ const ps = props.pageSize ?? props.dataProvider?.pageSize;
118
+ return isRef(ps) ? ps.value : ps?.value ?? ps ?? 10;
119
+ });
83
120
  const getRowKey = (row, index) => {
84
121
  const idKey = props.dataProvider?.idKey || "id";
85
122
  if (row && row[idKey] !== void 0 && row[idKey] !== null) {
@@ -116,6 +153,90 @@ const pageRows = computed(() => pageData.value || []);
116
153
  const isLoading = computed(() => {
117
154
  return dpLoading.value || dpInitialLoad.value || false;
118
155
  });
156
+ const datatableItems = computed(() => {
157
+ const currentRows = pageRows.value;
158
+ const dp = props.dataProvider;
159
+ if (props.virtualScroll) {
160
+ if (Array.isArray(dp?.allVisibleRows)) return dp.allVisibleRows;
161
+ if (Array.isArray(dp?.allVisibleRows?.value)) return dp.allVisibleRows.value;
162
+ if (Array.isArray(dp?.allVisibleNodes)) return dp.allVisibleNodes;
163
+ if (Array.isArray(dp?.allVisibleNodes?.value)) return dp.allVisibleNodes.value;
164
+ if (Array.isArray(dp?.allData?.value)) return dp.allData.value;
165
+ if (Array.isArray(dp?.allData)) return dp.allData;
166
+ if (Array.isArray(dp?.data)) return dp.data;
167
+ }
168
+ return currentRows;
169
+ });
170
+ const {
171
+ visibleItems,
172
+ startIndex,
173
+ endIndex,
174
+ topSpacerHeight,
175
+ bottomSpacerHeight,
176
+ containerRef,
177
+ onScroll,
178
+ scrollToIndex,
179
+ containerCssVars
180
+ } = useVirtualScroll({
181
+ items: datatableItems,
182
+ itemHeight: props.itemHeight || 50,
183
+ containerHeight: props.viewportHeight || 500,
184
+ buffer: props.buffer ?? 5,
185
+ virtualScroll: computed(() => !!props.virtualScroll),
186
+ onThresholdReach: () => {
187
+ const dp = props.dataProvider;
188
+ if (typeof dp?.loadMore === "function") {
189
+ dp.loadMore();
190
+ } else if (typeof dp?.nextPage === "function" && dp.hasMore) {
191
+ dp.nextPage();
192
+ }
193
+ }
194
+ });
195
+ const displayRows = computed(() => {
196
+ if (props.virtualScroll) {
197
+ return visibleItems.value;
198
+ }
199
+ return pageRows.value;
200
+ });
201
+ const totalColspan = computed(() => {
202
+ const fieldsCount = (props.visibleFields || props.dataProvider?.fields || []).length;
203
+ const checkCount = props.bulkActions && props.bulkActions.length > 0 ? 1 : 0;
204
+ const actionCount = props.rowActions && props.rowActions.length > 0 ? 1 : 0;
205
+ return fieldsCount + checkCount + actionCount;
206
+ });
207
+ const focusedIndex = ref(-1);
208
+ const handleKeyDown = (e) => {
209
+ if (!props.virtualScroll) return;
210
+ const total = datatableItems.value.length;
211
+ if (total === 0) return;
212
+ if (e.key === "ArrowDown") {
213
+ e.preventDefault();
214
+ focusedIndex.value = Math.min(total - 1, focusedIndex.value + 1);
215
+ scrollToIndex(focusedIndex.value);
216
+ } else if (e.key === "ArrowUp") {
217
+ e.preventDefault();
218
+ focusedIndex.value = Math.max(0, focusedIndex.value - 1);
219
+ scrollToIndex(focusedIndex.value);
220
+ } else if (e.key === "PageDown") {
221
+ e.preventDefault();
222
+ const step = Math.floor((props.viewportHeight || 500) / (props.itemHeight || 50));
223
+ focusedIndex.value = Math.min(total - 1, focusedIndex.value + step);
224
+ scrollToIndex(focusedIndex.value);
225
+ } else if (e.key === "PageUp") {
226
+ e.preventDefault();
227
+ const step = Math.floor((props.viewportHeight || 500) / (props.itemHeight || 50));
228
+ focusedIndex.value = Math.max(0, focusedIndex.value - step);
229
+ scrollToIndex(focusedIndex.value);
230
+ } else if (e.key === "Home") {
231
+ e.preventDefault();
232
+ focusedIndex.value = 0;
233
+ scrollToIndex(0);
234
+ } else if (e.key === "End") {
235
+ e.preventDefault();
236
+ focusedIndex.value = total - 1;
237
+ scrollToIndex(total - 1);
238
+ }
239
+ };
119
240
  </script>
120
241
 
121
242
  <style scoped>
@@ -190,62 +311,74 @@ const isLoading = computed(() => {
190
311
  text-align: right;
191
312
  }
192
313
  .modern-datatable table tbody tr {
193
- transition: all var(--transition-fast);
314
+ transition: background-color var(--transition-fast);
315
+ }
316
+ .modern-datatable table tbody tr:hover {
317
+ background-color: var(--table-row-hover-bg);
318
+ }
319
+ .modern-datatable table tbody tr.selected {
320
+ background-color: var(--table-row-selected-bg, rgba(var(--color-primary-rgb, 59, 130, 246), 0.08));
321
+ }
322
+ .modern-datatable table tbody tr.active-focus {
323
+ outline: 2px solid var(--color-primary, #3b82f6);
324
+ outline-offset: -2px;
194
325
  }
195
326
  .modern-datatable table tbody tr td {
196
327
  padding: var(--gap-md);
197
- border-bottom: 1px solid var(--neutral-100);
198
- color: var(--font-color-primary);
328
+ border-bottom: 1px solid var(--table-border-color);
329
+ color: var(--table-row-text-color);
199
330
  font-size: var(--font-size-md);
200
331
  }
201
332
  .modern-datatable table tbody tr td.check-cell {
202
333
  text-align: center;
203
334
  }
204
335
  .modern-datatable table tbody tr td.actions-cell {
205
- width: 1%;
206
- white-space: nowrap;
207
336
  text-align: right;
208
337
  }
338
+ .modern-datatable table tbody tr td.actions-cell .action-buttons {
339
+ display: inline-flex;
340
+ gap: var(--gap-xs);
341
+ justify-content: flex-end;
342
+ }
209
343
  .modern-datatable table tbody tr td.empty-state {
210
344
  text-align: center;
211
- padding: var(--gap-xl, 24px);
212
- color: var(--font-color-secondary, #666);
345
+ padding: var(--gap-xl);
346
+ color: var(--font-color-secondary);
213
347
  }
214
348
  .modern-datatable table tbody tr:last-child td {
215
349
  border-bottom: none;
216
350
  }
217
- .modern-datatable table tbody tr:hover {
218
- background-color: var(--table-row-hover);
219
- }
220
- .modern-datatable table tbody tr.selected {
221
- background-color: var(--table-row-selected);
222
- color: var(--table-row-selected-text, inherit);
223
- }
224
- .modern-datatable table tbody tr.selected td {
225
- border-bottom-color: rgba(255, 255, 255, 0.1);
226
- color: var(--table-row-selected-text, inherit);
227
- }
228
- .modern-datatable .action-buttons {
351
+ .modern-datatable .footer {
229
352
  display: flex;
230
- gap: var(--gap-sm);
231
353
  justify-content: flex-end;
354
+ align-items: center;
232
355
  }
233
- .modern-datatable .footer {
234
- padding: 4px 0;
356
+ .modern-datatable.is-virtual-scroll .k-datatable-wrapper {
357
+ overflow-y: auto;
358
+ max-height: var(--k-datatable-viewport-height, 500px);
359
+ outline: none;
235
360
  }
236
-
237
- .spinner {
238
- width: 40px;
239
- height: 40px;
240
- border: 3px solid var(--neutral-200);
241
- border-radius: 50%;
242
- border-top-color: var(--primary-500);
243
- animation: spin 1s ease-in-out infinite;
361
+ .modern-datatable.is-virtual-scroll table thead {
362
+ position: sticky;
363
+ top: 0;
364
+ z-index: 2;
365
+ background: var(--table-header-bg, #ffffff);
244
366
  }
245
-
246
- @keyframes spin {
247
- to {
248
- transform: rotate(360deg);
249
- }
367
+ .modern-datatable.is-virtual-scroll table thead th {
368
+ background: var(--table-header-bg, #ffffff);
369
+ }
370
+ .modern-datatable.is-virtual-scroll tr.k-virtual-spacer {
371
+ height: var(--spacer-height, 0px);
372
+ border: none;
373
+ padding: 0;
374
+ margin: 0;
375
+ background: transparent !important;
376
+ }
377
+ .modern-datatable.is-virtual-scroll tr.k-virtual-spacer td {
378
+ height: var(--spacer-height, 0px);
379
+ border: none;
380
+ padding: 0;
381
+ margin: 0;
382
+ background: transparent !important;
250
383
  }
251
384
  </style>
@@ -7,22 +7,33 @@ type __VLS_Props = {
7
7
  bulkActions?: Array<IKDatatableAction>;
8
8
  selectedRows?: any[];
9
9
  selectAll?: boolean;
10
+ currentPage?: number;
11
+ pageSize?: number;
10
12
  cellSlots?: Record<string, any>;
11
13
  headerSlots?: Record<string, any>;
12
14
  columnHeaders?: Record<string, string>;
13
15
  columnWidths?: Record<string, string>;
14
16
  emptyStateText?: string;
15
17
  emptyFilterStateText?: string;
18
+ virtualScroll?: boolean;
19
+ itemHeight?: number;
20
+ viewportHeight?: number;
21
+ buffer?: number;
16
22
  };
17
23
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
18
24
  "update:selectedRows": (value: any[]) => any;
19
25
  "update:selectAll": (value: boolean) => any;
26
+ "update:currentPage": (value: number) => any;
27
+ "update:pageSize": (value: number) => any;
20
28
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
21
29
  "onUpdate:selectedRows"?: ((value: any[]) => any) | undefined;
22
30
  "onUpdate:selectAll"?: ((value: boolean) => any) | undefined;
31
+ "onUpdate:currentPage"?: ((value: number) => any) | undefined;
32
+ "onUpdate:pageSize"?: ((value: number) => any) | undefined;
23
33
  }>, {
24
34
  selectedRows: any[];
25
35
  selectAll: boolean;
36
+ buffer: number;
26
37
  bulkActions: Array<IKDatatableAction>;
27
38
  visibleFields: Array<string> | null;
28
39
  rowActions: Array<IKDatatableAction>;
@@ -32,6 +43,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
32
43
  columnWidths: Record<string, string>;
33
44
  emptyStateText: string;
34
45
  emptyFilterStateText: string;
46
+ virtualScroll: boolean;
47
+ itemHeight: number;
48
+ viewportHeight: number;
35
49
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
36
50
  declare const _default: typeof __VLS_export;
37
51
  export default _default;
@@ -1,4 +1,5 @@
1
1
  type __VLS_Props = {
2
+ modelValue?: any;
2
3
  model?: any;
3
4
  value?: any;
4
5
  disabled?: boolean;
@@ -21,6 +21,7 @@
21
21
  <script setup>
22
22
  import { computed } from "vue";
23
23
  const props = defineProps({
24
+ modelValue: { type: null, required: false },
24
25
  model: { type: null, required: false },
25
26
  value: { type: null, required: false },
26
27
  disabled: { type: Boolean, required: false },
@@ -30,7 +31,7 @@ const props = defineProps({
30
31
  });
31
32
  const emit = defineEmits(["update:modelValue", "update:model"]);
32
33
  const internalModel = computed({
33
- get: () => props.model,
34
+ get: () => props.modelValue !== void 0 ? props.modelValue : props.model,
34
35
  set: (val) => {
35
36
  emit("update:model", val);
36
37
  emit("update:modelValue", val);
@@ -1,4 +1,5 @@
1
1
  type __VLS_Props = {
2
+ modelValue?: any;
2
3
  model?: any;
3
4
  value?: any;
4
5
  disabled?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@katlux/preset-modern",
3
- "version": "0.1.1-alpha.95",
3
+ "version": "0.1.1-alpha.97",
4
4
  "description": "Modern default design preset and styling variables for Katlux components",
5
5
  "author": "Katlux",
6
6
  "license": "Apache-2.0",
@@ -20,7 +20,7 @@
20
20
  "./dist/runtime/assets/*": "./dist/runtime/assets/*"
21
21
  },
22
22
  "dependencies": {
23
- "@katlux/toolkit": "*",
23
+ "@katlux/toolkit": "^0.1.1-alpha.120",
24
24
  "@nuxt/kit": "^3.12.0"
25
25
  },
26
26
  "files": [