@pyreweb/fabric 1.2.6
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/README.md +119 -0
- package/dist/fabric.cjs.js +18109 -0
- package/dist/fabric.css +2180 -0
- package/dist/fabric.esm.js +18062 -0
- package/dist/fabric.min.js +18112 -0
- package/dist/types/components/atoms/FAvatar/FAvatar.test.d.ts +1 -0
- package/dist/types/components/atoms/FBadge/FBadge.test.d.ts +1 -0
- package/dist/types/components/atoms/FButton/FButton.test.d.ts +1 -0
- package/dist/types/components/atoms/FCheckbox/FCheckbox.test.d.ts +1 -0
- package/dist/types/components/atoms/FDivider/FDivider.test.d.ts +1 -0
- package/dist/types/components/atoms/FIcon/FIcon.test.d.ts +1 -0
- package/dist/types/components/atoms/FInput/FInput.test.d.ts +1 -0
- package/dist/types/components/atoms/FLoader/FLoader.test.d.ts +1 -0
- package/dist/types/components/atoms/FRadio/FRadio.test.d.ts +1 -0
- package/dist/types/components/atoms/FTextarea/FTextarea.test.d.ts +1 -0
- package/dist/types/components/atoms/FToggle/FToggle.test.d.ts +1 -0
- package/dist/types/components/atoms/FTypography/FTypography.test.d.ts +1 -0
- package/dist/types/components/atoms/index.d.ts +13 -0
- package/dist/types/components/molecules/FAccordionItem/FAccordionItem.test.d.ts +1 -0
- package/dist/types/components/molecules/FAlert/FAlert.test.d.ts +1 -0
- package/dist/types/components/molecules/FBreadcrumb/FBreadcrumb.test.d.ts +1 -0
- package/dist/types/components/molecules/FButtonGroup/FButtonGroup.test.d.ts +1 -0
- package/dist/types/components/molecules/FCard/FCard.test.d.ts +1 -0
- package/dist/types/components/molecules/FDatePicker/FDatePicker.test.d.ts +1 -0
- package/dist/types/components/molecules/FEmptyState/FEmptyState.test.d.ts +1 -0
- package/dist/types/components/molecules/FFilePreview/FFilePreview.test.d.ts +1 -0
- package/dist/types/components/molecules/FFormField/FFormField.test.d.ts +1 -0
- package/dist/types/components/molecules/FListItem/FListItem.test.d.ts +1 -0
- package/dist/types/components/molecules/FPagination/FPagination.test.d.ts +1 -0
- package/dist/types/components/molecules/FSearchBar/FSearchBar.test.d.ts +1 -0
- package/dist/types/components/molecules/FSelect/FSelect.test.d.ts +1 -0
- package/dist/types/components/molecules/FStatCard/FStatCard.test.d.ts +1 -0
- package/dist/types/components/molecules/FTabs/FTabs.test.d.ts +1 -0
- package/dist/types/components/molecules/FToast/FToast.test.d.ts +1 -0
- package/dist/types/components/molecules/index.d.ts +18 -0
- package/dist/types/components/organisms/FActivityFeed/FActivityFeed.test.d.ts +1 -0
- package/dist/types/components/organisms/FDataTable/FDataTable.test.d.ts +1 -0
- package/dist/types/components/organisms/FDrawer/FDrawer.test.d.ts +1 -0
- package/dist/types/components/organisms/FFileUpload/FFileUpload.test.d.ts +1 -0
- package/dist/types/components/organisms/FFilterSidebar/FFilterSidebar.test.d.ts +1 -0
- package/dist/types/components/organisms/FForm/FForm.test.d.ts +1 -0
- package/dist/types/components/organisms/FModal/FModal.test.d.ts +1 -0
- package/dist/types/components/organisms/FNavigationSidebar/FNavigationSidebar.test.d.ts +1 -0
- package/dist/types/components/organisms/FOnboardingStepper/FOnboardingStepper.test.d.ts +1 -0
- package/dist/types/components/organisms/FOnboardingStepper/FStepperProgress.test.d.ts +1 -0
- package/dist/types/components/organisms/FPageHeader/FPageHeader.test.d.ts +1 -0
- package/dist/types/components/organisms/FProfileSection/FProfileSection.test.d.ts +1 -0
- package/dist/types/components/organisms/FToastProvider/FToastProvider.test.d.ts +1 -0
- package/dist/types/components/organisms/FUserMenu/FUserMenu.test.d.ts +1 -0
- package/dist/types/components/organisms/index.d.ts +14 -0
- package/dist/types/components/utils/FThemeProvider.test.d.ts +1 -0
- package/dist/types/components/utils/index.d.ts +2 -0
- package/dist/types/components.d.ts +602 -0
- package/dist/types/composables/index.d.ts +12 -0
- package/dist/types/composables/useDataTableState.d.ts +106 -0
- package/dist/types/composables/useDataTableState.test.d.ts +1 -0
- package/dist/types/composables/useFormValidation.d.ts +49 -0
- package/dist/types/composables/useFormValidation.test.d.ts +1 -0
- package/dist/types/composables/useSidebarState.d.ts +65 -0
- package/dist/types/composables/useSidebarState.test.d.ts +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/types.d.ts +529 -0
- package/package.json +100 -0
- package/src/components/atoms/FAvatar/FAvatar.stories.js +100 -0
- package/src/components/atoms/FAvatar/FAvatar.test.ts +95 -0
- package/src/components/atoms/FAvatar/FAvatar.vue +190 -0
- package/src/components/atoms/FBadge/FBadge.stories.js +129 -0
- package/src/components/atoms/FBadge/FBadge.test.ts +93 -0
- package/src/components/atoms/FBadge/FBadge.vue +103 -0
- package/src/components/atoms/FButton/FButton.stories.js +122 -0
- package/src/components/atoms/FButton/FButton.test.ts +98 -0
- package/src/components/atoms/FButton/FButton.vue +147 -0
- package/src/components/atoms/FCheckbox/FCheckbox.stories.js +96 -0
- package/src/components/atoms/FCheckbox/FCheckbox.test.ts +64 -0
- package/src/components/atoms/FCheckbox/FCheckbox.vue +76 -0
- package/src/components/atoms/FDivider/FDivider.stories.js +104 -0
- package/src/components/atoms/FDivider/FDivider.test.ts +80 -0
- package/src/components/atoms/FDivider/FDivider.vue +117 -0
- package/src/components/atoms/FIcon/FIcon.stories.js +189 -0
- package/src/components/atoms/FIcon/FIcon.test.ts +99 -0
- package/src/components/atoms/FIcon/FIcon.vue +192 -0
- package/src/components/atoms/FInput/FInput.stories.js +119 -0
- package/src/components/atoms/FInput/FInput.test.ts +79 -0
- package/src/components/atoms/FInput/FInput.vue +88 -0
- package/src/components/atoms/FLoader/FLoader.stories.js +109 -0
- package/src/components/atoms/FLoader/FLoader.test.ts +66 -0
- package/src/components/atoms/FLoader/FLoader.vue +97 -0
- package/src/components/atoms/FRadio/FRadio.stories.js +105 -0
- package/src/components/atoms/FRadio/FRadio.test.ts +75 -0
- package/src/components/atoms/FRadio/FRadio.vue +119 -0
- package/src/components/atoms/FTextarea/FTextarea.stories.js +126 -0
- package/src/components/atoms/FTextarea/FTextarea.test.ts +94 -0
- package/src/components/atoms/FTextarea/FTextarea.vue +156 -0
- package/src/components/atoms/FToggle/FToggle.stories.js +108 -0
- package/src/components/atoms/FToggle/FToggle.test.ts +96 -0
- package/src/components/atoms/FToggle/FToggle.vue +123 -0
- package/src/components/atoms/FTypography/FTypography.stories.js +127 -0
- package/src/components/atoms/FTypography/FTypography.test.ts +93 -0
- package/src/components/atoms/FTypography/FTypography.vue +78 -0
- package/src/components/atoms/index.ts +27 -0
- package/src/components/molecules/FAccordionItem/FAccordionItem.stories.js +71 -0
- package/src/components/molecules/FAccordionItem/FAccordionItem.test.ts +61 -0
- package/src/components/molecules/FAccordionItem/FAccordionItem.vue +105 -0
- package/src/components/molecules/FAlert/FAlert.stories.js +87 -0
- package/src/components/molecules/FAlert/FAlert.test.ts +59 -0
- package/src/components/molecules/FAlert/FAlert.vue +108 -0
- package/src/components/molecules/FBreadcrumb/FBreadcrumb.stories.js +90 -0
- package/src/components/molecules/FBreadcrumb/FBreadcrumb.test.ts +76 -0
- package/src/components/molecules/FBreadcrumb/FBreadcrumb.vue +117 -0
- package/src/components/molecules/FButtonGroup/FButtonGroup.stories.js +82 -0
- package/src/components/molecules/FButtonGroup/FButtonGroup.test.ts +44 -0
- package/src/components/molecules/FButtonGroup/FButtonGroup.vue +31 -0
- package/src/components/molecules/FCard/FCard.stories.js +136 -0
- package/src/components/molecules/FCard/FCard.test.ts +87 -0
- package/src/components/molecules/FCard/FCard.vue +75 -0
- package/src/components/molecules/FDatePicker/FDatePicker.stories.js +305 -0
- package/src/components/molecules/FDatePicker/FDatePicker.test.ts +282 -0
- package/src/components/molecules/FDatePicker/FDatePicker.vue +750 -0
- package/src/components/molecules/FEmptyState/FEmptyState.stories.js +98 -0
- package/src/components/molecules/FEmptyState/FEmptyState.test.ts +82 -0
- package/src/components/molecules/FEmptyState/FEmptyState.vue +89 -0
- package/src/components/molecules/FFilePreview/FFilePreview.stories.js +130 -0
- package/src/components/molecules/FFilePreview/FFilePreview.test.ts +70 -0
- package/src/components/molecules/FFilePreview/FFilePreview.vue +125 -0
- package/src/components/molecules/FFormField/FFormField.stories.js +149 -0
- package/src/components/molecules/FFormField/FFormField.test.ts +85 -0
- package/src/components/molecules/FFormField/FFormField.vue +107 -0
- package/src/components/molecules/FListItem/FListItem.stories.js +158 -0
- package/src/components/molecules/FListItem/FListItem.test.ts +93 -0
- package/src/components/molecules/FListItem/FListItem.vue +113 -0
- package/src/components/molecules/FPagination/FPagination.stories.js +132 -0
- package/src/components/molecules/FPagination/FPagination.test.ts +79 -0
- package/src/components/molecules/FPagination/FPagination.vue +206 -0
- package/src/components/molecules/FSearchBar/FSearchBar.stories.js +129 -0
- package/src/components/molecules/FSearchBar/FSearchBar.test.ts +81 -0
- package/src/components/molecules/FSearchBar/FSearchBar.vue +180 -0
- package/src/components/molecules/FSelect/FSelect.stories.js +333 -0
- package/src/components/molecules/FSelect/FSelect.test.ts +478 -0
- package/src/components/molecules/FSelect/FSelect.vue +551 -0
- package/src/components/molecules/FStatCard/FStatCard.stories.js +144 -0
- package/src/components/molecules/FStatCard/FStatCard.test.ts +78 -0
- package/src/components/molecules/FStatCard/FStatCard.vue +106 -0
- package/src/components/molecules/FTabs/FTab.vue +63 -0
- package/src/components/molecules/FTabs/FTabs.stories.js +277 -0
- package/src/components/molecules/FTabs/FTabs.test.ts +264 -0
- package/src/components/molecules/FTabs/FTabs.vue +273 -0
- package/src/components/molecules/FToast/FToast.stories.js +150 -0
- package/src/components/molecules/FToast/FToast.test.ts +157 -0
- package/src/components/molecules/FToast/FToast.vue +283 -0
- package/src/components/molecules/index.ts +37 -0
- package/src/components/organisms/FActivityFeed/FActivityFeed.stories.js +217 -0
- package/src/components/organisms/FActivityFeed/FActivityFeed.test.ts +134 -0
- package/src/components/organisms/FActivityFeed/FActivityFeed.vue +589 -0
- package/src/components/organisms/FDataTable/FDataTable.stories.js +370 -0
- package/src/components/organisms/FDataTable/FDataTable.test.ts +248 -0
- package/src/components/organisms/FDataTable/FDataTable.vue +808 -0
- package/src/components/organisms/FDrawer/FDrawer.stories.js +296 -0
- package/src/components/organisms/FDrawer/FDrawer.test.ts +142 -0
- package/src/components/organisms/FDrawer/FDrawer.vue +303 -0
- package/src/components/organisms/FFileUpload/FFileUpload.stories.js +162 -0
- package/src/components/organisms/FFileUpload/FFileUpload.test.ts +103 -0
- package/src/components/organisms/FFileUpload/FFileUpload.vue +616 -0
- package/src/components/organisms/FFilterSidebar/FFilterSidebar.stories.js +161 -0
- package/src/components/organisms/FFilterSidebar/FFilterSidebar.test.ts +92 -0
- package/src/components/organisms/FFilterSidebar/FFilterSidebar.vue +458 -0
- package/src/components/organisms/FForm/FForm.stories.js +270 -0
- package/src/components/organisms/FForm/FForm.test.ts +63 -0
- package/src/components/organisms/FForm/FForm.vue +19 -0
- package/src/components/organisms/FModal/FModal.stories.js +227 -0
- package/src/components/organisms/FModal/FModal.test.ts +181 -0
- package/src/components/organisms/FModal/FModal.vue +319 -0
- package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.stories.js +176 -0
- package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.test.ts +95 -0
- package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.vue +577 -0
- package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.stories.js +197 -0
- package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.test.ts +114 -0
- package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.vue +212 -0
- package/src/components/organisms/FOnboardingStepper/FStepperProgress.stories.js +122 -0
- package/src/components/organisms/FOnboardingStepper/FStepperProgress.test.ts +130 -0
- package/src/components/organisms/FOnboardingStepper/FStepperProgress.vue +146 -0
- package/src/components/organisms/FPageHeader/FPageHeader.stories.js +142 -0
- package/src/components/organisms/FPageHeader/FPageHeader.test.ts +83 -0
- package/src/components/organisms/FPageHeader/FPageHeader.vue +241 -0
- package/src/components/organisms/FProfileSection/FProfileSection.stories.js +190 -0
- package/src/components/organisms/FProfileSection/FProfileSection.test.ts +85 -0
- package/src/components/organisms/FProfileSection/FProfileSection.vue +562 -0
- package/src/components/organisms/FToastProvider/FToastProvider.stories.js +290 -0
- package/src/components/organisms/FToastProvider/FToastProvider.test.ts +215 -0
- package/src/components/organisms/FToastProvider/FToastProvider.vue +214 -0
- package/src/components/organisms/FUserMenu/FUserMenu.stories.js +170 -0
- package/src/components/organisms/FUserMenu/FUserMenu.test.ts +102 -0
- package/src/components/organisms/FUserMenu/FUserMenu.vue +407 -0
- package/src/components/organisms/index.ts +29 -0
- package/src/components/utils/FThemeProvider.stories.js +236 -0
- package/src/components/utils/FThemeProvider.test.ts +244 -0
- package/src/components/utils/FThemeProvider.vue +191 -0
- package/src/components/utils/index.ts +3 -0
- package/src/components.d.ts +602 -0
- package/src/composables/README.md +233 -0
- package/src/composables/index.ts +25 -0
- package/src/composables/useDataTableState.test.ts +378 -0
- package/src/composables/useDataTableState.ts +361 -0
- package/src/composables/useFormValidation.test.ts +198 -0
- package/src/composables/useFormValidation.ts +178 -0
- package/src/composables/useSidebarState.test.ts +307 -0
- package/src/composables/useSidebarState.ts +201 -0
- package/src/env.d.ts +14 -0
- package/src/index.ts +167 -0
- package/src/styles/tailwind.css +173 -0
- package/src/types.ts +740 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { ref, computed, watch, Ref, ComputedRef } from 'vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for useDataTableState
|
|
6
|
+
*/
|
|
7
|
+
export interface DataTableStateOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Table data
|
|
10
|
+
*/
|
|
11
|
+
data: any[];
|
|
12
|
+
/**
|
|
13
|
+
* Column definitions
|
|
14
|
+
*/
|
|
15
|
+
columns: Array<{
|
|
16
|
+
key: string;
|
|
17
|
+
label: string;
|
|
18
|
+
sortable?: boolean;
|
|
19
|
+
align?: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Unique key property in data objects
|
|
23
|
+
*/
|
|
24
|
+
rowKey?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Initial page number
|
|
27
|
+
*/
|
|
28
|
+
initialPage?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Items per page
|
|
31
|
+
*/
|
|
32
|
+
perPage?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Enable pagination
|
|
35
|
+
*/
|
|
36
|
+
paginated?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Enable virtualization (disables pagination)
|
|
39
|
+
*/
|
|
40
|
+
virtual?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Default sort key
|
|
43
|
+
*/
|
|
44
|
+
defaultSortKey?: string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Default sort direction
|
|
47
|
+
*/
|
|
48
|
+
defaultSortDirection?: 'asc' | 'desc';
|
|
49
|
+
/**
|
|
50
|
+
* Server mode (external data fetching)
|
|
51
|
+
*/
|
|
52
|
+
serverMode?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Total items for server-side pagination
|
|
55
|
+
*/
|
|
56
|
+
totalItems?: number | null;
|
|
57
|
+
/**
|
|
58
|
+
* Initially selected row keys
|
|
59
|
+
*/
|
|
60
|
+
selected?: any[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Return type for useDataTableState
|
|
65
|
+
*/
|
|
66
|
+
export interface DataTableState {
|
|
67
|
+
// Search state
|
|
68
|
+
searchQuery: Ref<string>;
|
|
69
|
+
|
|
70
|
+
// Sort state
|
|
71
|
+
sortKey: Ref<string | null>;
|
|
72
|
+
sortDirection: Ref<'asc' | 'desc'>;
|
|
73
|
+
|
|
74
|
+
// Pagination state
|
|
75
|
+
internalPage: Ref<number>;
|
|
76
|
+
totalPages: ComputedRef<number>;
|
|
77
|
+
paginationInfo: ComputedRef<string>;
|
|
78
|
+
effectivePaginated: ComputedRef<boolean>;
|
|
79
|
+
|
|
80
|
+
// Selection state
|
|
81
|
+
selectedKeys: Ref<any[]>;
|
|
82
|
+
selectedKeysSet: ComputedRef<Set<any>>;
|
|
83
|
+
selectedItems: ComputedRef<any[]>;
|
|
84
|
+
isAllSelected: ComputedRef<boolean>;
|
|
85
|
+
|
|
86
|
+
// Data processing
|
|
87
|
+
filteredData: ComputedRef<any[]>;
|
|
88
|
+
sortedData: ComputedRef<any[]>;
|
|
89
|
+
processedData: ComputedRef<any[]>;
|
|
90
|
+
paginatedData: ComputedRef<any[]>;
|
|
91
|
+
computedTotalItems: ComputedRef<number>;
|
|
92
|
+
|
|
93
|
+
// Methods
|
|
94
|
+
getCellValue: (row: any, key: string) => any;
|
|
95
|
+
getRowKey: (row: any, index?: number) => any;
|
|
96
|
+
handleSort: (key: string) => void;
|
|
97
|
+
isRowSelected: (row: any) => boolean;
|
|
98
|
+
handleRowSelect: (row: any, checked: boolean) => void;
|
|
99
|
+
handleSelectAll: (checked: boolean) => void;
|
|
100
|
+
clearSelection: () => void;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Composable for managing data table state and logic
|
|
105
|
+
*
|
|
106
|
+
* Handles filtering, sorting, pagination, and row selection for data tables.
|
|
107
|
+
*
|
|
108
|
+
* @param options - Configuration options for the data table state
|
|
109
|
+
* @param emit - Emit function from the component setup
|
|
110
|
+
* @returns Object containing reactive state and methods for table operations
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const tableState = useDataTableState({
|
|
115
|
+
* data: myData,
|
|
116
|
+
* columns: myColumns,
|
|
117
|
+
* perPage: 10
|
|
118
|
+
* }, emit);
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export function useDataTableState(
|
|
122
|
+
options: DataTableStateOptions,
|
|
123
|
+
emit: (event: string, ...args: any[]) => void
|
|
124
|
+
): DataTableState {
|
|
125
|
+
const {
|
|
126
|
+
data,
|
|
127
|
+
columns,
|
|
128
|
+
rowKey = 'id',
|
|
129
|
+
initialPage = 1,
|
|
130
|
+
perPage = 10,
|
|
131
|
+
paginated = true,
|
|
132
|
+
virtual = false,
|
|
133
|
+
defaultSortKey = null,
|
|
134
|
+
defaultSortDirection = 'asc',
|
|
135
|
+
serverMode = false,
|
|
136
|
+
totalItems = null,
|
|
137
|
+
selected = []
|
|
138
|
+
} = options;
|
|
139
|
+
|
|
140
|
+
// Reactive state
|
|
141
|
+
const searchQuery = ref('');
|
|
142
|
+
const sortKey = ref<string | null>(defaultSortKey);
|
|
143
|
+
const sortDirection = ref<'asc' | 'desc'>(defaultSortDirection);
|
|
144
|
+
const internalPage = ref(initialPage);
|
|
145
|
+
const selectedKeys = ref([...selected]);
|
|
146
|
+
|
|
147
|
+
// Helper: Get cell value with support for nested keys
|
|
148
|
+
const getCellValue = (row: any, key: string): any => {
|
|
149
|
+
return key.split('.').reduce((obj, k) => obj?.[k], row);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Helper: Get row key
|
|
153
|
+
const getRowKey = (row: any, index?: number): any => {
|
|
154
|
+
return row[rowKey] ?? index;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Computed: Filtered data (client-side only)
|
|
158
|
+
const filteredData = computed(() => {
|
|
159
|
+
if (serverMode || !searchQuery.value) {
|
|
160
|
+
return data;
|
|
161
|
+
}
|
|
162
|
+
const query = searchQuery.value.toLowerCase();
|
|
163
|
+
return data.filter((row) => {
|
|
164
|
+
return columns.some((column) => {
|
|
165
|
+
const value = getCellValue(row, column.key);
|
|
166
|
+
return String(value).toLowerCase().includes(query);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Computed: Sorted data (client-side only)
|
|
172
|
+
const sortedData = computed(() => {
|
|
173
|
+
if (serverMode || !sortKey.value) {
|
|
174
|
+
return filteredData.value;
|
|
175
|
+
}
|
|
176
|
+
return [...filteredData.value].sort((a, b) => {
|
|
177
|
+
const aValue = getCellValue(a, sortKey.value as string);
|
|
178
|
+
const bValue = getCellValue(b, sortKey.value as string);
|
|
179
|
+
|
|
180
|
+
let comparison = 0;
|
|
181
|
+
if (aValue === null || aValue === undefined) comparison = 1;
|
|
182
|
+
else if (bValue === null || bValue === undefined) comparison = -1;
|
|
183
|
+
else if (typeof aValue === 'string') {
|
|
184
|
+
comparison = aValue.localeCompare(bValue);
|
|
185
|
+
} else {
|
|
186
|
+
comparison = aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return sortDirection.value === 'desc' ? -comparison : comparison;
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Computed: Processed data after filtering and sorting
|
|
194
|
+
const processedData = computed(() => sortedData.value);
|
|
195
|
+
|
|
196
|
+
// Computed: Total items for pagination
|
|
197
|
+
const computedTotalItems = computed(() => {
|
|
198
|
+
if (serverMode && totalItems !== null) {
|
|
199
|
+
return totalItems;
|
|
200
|
+
}
|
|
201
|
+
return processedData.value.length;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Computed: Effective pagination (disabled when virtual mode is enabled)
|
|
205
|
+
const effectivePaginated = computed(() => (virtual ? false : paginated));
|
|
206
|
+
|
|
207
|
+
// Computed: Total pages
|
|
208
|
+
const totalPages = computed(() => {
|
|
209
|
+
if (!effectivePaginated.value) return 1;
|
|
210
|
+
return Math.max(1, Math.ceil(computedTotalItems.value / perPage));
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Computed: Paginated data (client-side only)
|
|
214
|
+
const paginatedData = computed(() => {
|
|
215
|
+
if (serverMode || !effectivePaginated.value) {
|
|
216
|
+
return processedData.value;
|
|
217
|
+
}
|
|
218
|
+
const start = (internalPage.value - 1) * perPage;
|
|
219
|
+
const end = start + perPage;
|
|
220
|
+
return processedData.value.slice(start, end);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Computed: Pagination info text
|
|
224
|
+
const paginationInfo = computed(() => {
|
|
225
|
+
if (!effectivePaginated.value) {
|
|
226
|
+
return `${computedTotalItems.value} élément(s)`;
|
|
227
|
+
}
|
|
228
|
+
const start = Math.min(
|
|
229
|
+
(internalPage.value - 1) * perPage + 1,
|
|
230
|
+
computedTotalItems.value
|
|
231
|
+
);
|
|
232
|
+
const end = Math.min(
|
|
233
|
+
internalPage.value * perPage,
|
|
234
|
+
computedTotalItems.value
|
|
235
|
+
);
|
|
236
|
+
return `${start} - ${end} sur ${computedTotalItems.value}`;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Computed: Selected keys as a Set for efficient lookups
|
|
240
|
+
const selectedKeysSet = computed(() => new Set(selectedKeys.value));
|
|
241
|
+
|
|
242
|
+
// Computed: Selected items
|
|
243
|
+
const selectedItems = computed(() => {
|
|
244
|
+
return data.filter((row) => selectedKeysSet.value.has(getRowKey(row)));
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Computed: Is all selected
|
|
248
|
+
const isAllSelected = computed(() => {
|
|
249
|
+
if (paginatedData.value.length === 0) return false;
|
|
250
|
+
return paginatedData.value.every((row) => isRowSelected(row));
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Method: Check if row is selected
|
|
254
|
+
const isRowSelected = (row: any): boolean => {
|
|
255
|
+
return selectedKeysSet.value.has(getRowKey(row));
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Method: Handle sort
|
|
259
|
+
const handleSort = (key: string): void => {
|
|
260
|
+
if (sortKey.value === key) {
|
|
261
|
+
sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc';
|
|
262
|
+
} else {
|
|
263
|
+
sortKey.value = key;
|
|
264
|
+
sortDirection.value = 'asc';
|
|
265
|
+
}
|
|
266
|
+
emit('sort', { key: sortKey.value, direction: sortDirection.value });
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Method: Handle row selection
|
|
270
|
+
const handleRowSelect = (row: any, checked: boolean): void => {
|
|
271
|
+
const key = getRowKey(row);
|
|
272
|
+
if (checked) {
|
|
273
|
+
if (!selectedKeysSet.value.has(key)) {
|
|
274
|
+
selectedKeys.value = [...selectedKeys.value, key];
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
selectedKeys.value = selectedKeys.value.filter((k) => k !== key);
|
|
278
|
+
}
|
|
279
|
+
emit('select', { row, selected: checked });
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Method: Handle select all
|
|
283
|
+
const handleSelectAll = (checked: boolean): void => {
|
|
284
|
+
if (checked) {
|
|
285
|
+
const currentKeys = paginatedData.value.map((row) => getRowKey(row));
|
|
286
|
+
const newKeys = currentKeys.filter(
|
|
287
|
+
(k) => !selectedKeys.value.includes(k)
|
|
288
|
+
);
|
|
289
|
+
selectedKeys.value = [...selectedKeys.value, ...newKeys];
|
|
290
|
+
} else {
|
|
291
|
+
const currentKeys = paginatedData.value.map((row) => getRowKey(row));
|
|
292
|
+
selectedKeys.value = selectedKeys.value.filter(
|
|
293
|
+
(k) => !currentKeys.includes(k)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
emit('select-all', checked);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// Method: Clear selection
|
|
300
|
+
const clearSelection = (): void => {
|
|
301
|
+
selectedKeys.value = [];
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// Watch: Reset to first page when search changes (client-side only)
|
|
305
|
+
watch(searchQuery, () => {
|
|
306
|
+
if (!serverMode) {
|
|
307
|
+
internalPage.value = 1;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Watch: Emit page changes
|
|
312
|
+
watch(internalPage, (newVal) => {
|
|
313
|
+
emit('update:page', newVal);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Watch: Emit selection changes
|
|
317
|
+
watch(
|
|
318
|
+
selectedKeys,
|
|
319
|
+
(newVal) => {
|
|
320
|
+
emit('update:selected', newVal);
|
|
321
|
+
},
|
|
322
|
+
{ deep: true }
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
// Search state
|
|
327
|
+
searchQuery,
|
|
328
|
+
|
|
329
|
+
// Sort state
|
|
330
|
+
sortKey,
|
|
331
|
+
sortDirection,
|
|
332
|
+
|
|
333
|
+
// Pagination state
|
|
334
|
+
internalPage,
|
|
335
|
+
totalPages,
|
|
336
|
+
paginationInfo,
|
|
337
|
+
effectivePaginated,
|
|
338
|
+
|
|
339
|
+
// Selection state
|
|
340
|
+
selectedKeys,
|
|
341
|
+
selectedKeysSet,
|
|
342
|
+
selectedItems,
|
|
343
|
+
isAllSelected,
|
|
344
|
+
|
|
345
|
+
// Data processing
|
|
346
|
+
filteredData,
|
|
347
|
+
sortedData,
|
|
348
|
+
processedData,
|
|
349
|
+
paginatedData,
|
|
350
|
+
computedTotalItems,
|
|
351
|
+
|
|
352
|
+
// Methods
|
|
353
|
+
getCellValue,
|
|
354
|
+
getRowKey,
|
|
355
|
+
handleSort,
|
|
356
|
+
isRowSelected,
|
|
357
|
+
handleRowSelect,
|
|
358
|
+
handleSelectAll,
|
|
359
|
+
clearSelection
|
|
360
|
+
};
|
|
361
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { useFormValidation } from './useFormValidation';
|
|
3
|
+
|
|
4
|
+
describe('useFormValidation', () => {
|
|
5
|
+
it('initializes with default state', () => {
|
|
6
|
+
const emit = vi.fn();
|
|
7
|
+
const state = useFormValidation({}, emit);
|
|
8
|
+
|
|
9
|
+
expect(state.formData.value).toEqual({});
|
|
10
|
+
expect(state.errors.value).toEqual({});
|
|
11
|
+
expect(state.isValid.value).toBe(true);
|
|
12
|
+
expect(state.isSubmitting.value).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('initializes with initial data', () => {
|
|
16
|
+
const emit = vi.fn();
|
|
17
|
+
const initialData = { name: 'John', email: 'john@test.com' };
|
|
18
|
+
const state = useFormValidation({ initialData }, emit);
|
|
19
|
+
|
|
20
|
+
expect(state.formData.value).toEqual(initialData);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('setFieldValue', () => {
|
|
24
|
+
it('sets a field value', () => {
|
|
25
|
+
const emit = vi.fn();
|
|
26
|
+
const state = useFormValidation({}, emit);
|
|
27
|
+
|
|
28
|
+
state.setFieldValue('name', 'Alice');
|
|
29
|
+
expect(state.formData.value.name).toBe('Alice');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('clears field error when value is updated', () => {
|
|
33
|
+
const emit = vi.fn();
|
|
34
|
+
const state = useFormValidation({}, emit);
|
|
35
|
+
|
|
36
|
+
state.setFieldError('name', 'Required');
|
|
37
|
+
expect(state.errors.value.name).toBe('Required');
|
|
38
|
+
|
|
39
|
+
state.setFieldValue('name', 'Alice');
|
|
40
|
+
expect(state.errors.value.name).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('error handling', () => {
|
|
45
|
+
it('sets a field error', () => {
|
|
46
|
+
const emit = vi.fn();
|
|
47
|
+
const state = useFormValidation({}, emit);
|
|
48
|
+
|
|
49
|
+
state.setFieldError('email', 'Invalid email');
|
|
50
|
+
expect(state.errors.value.email).toBe('Invalid email');
|
|
51
|
+
expect(state.isValid.value).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('clears a specific field error', () => {
|
|
55
|
+
const emit = vi.fn();
|
|
56
|
+
const state = useFormValidation({}, emit);
|
|
57
|
+
|
|
58
|
+
state.setFieldError('email', 'Invalid email');
|
|
59
|
+
state.setFieldError('name', 'Required');
|
|
60
|
+
|
|
61
|
+
state.clearFieldError('email');
|
|
62
|
+
expect(state.errors.value.email).toBeUndefined();
|
|
63
|
+
expect(state.errors.value.name).toBe('Required');
|
|
64
|
+
expect(state.isValid.value).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('updates isValid when all errors are cleared individually', () => {
|
|
68
|
+
const emit = vi.fn();
|
|
69
|
+
const state = useFormValidation({}, emit);
|
|
70
|
+
|
|
71
|
+
state.setFieldError('email', 'Invalid email');
|
|
72
|
+
state.setFieldError('name', 'Required');
|
|
73
|
+
expect(state.isValid.value).toBe(false);
|
|
74
|
+
|
|
75
|
+
state.clearFieldError('email');
|
|
76
|
+
state.clearFieldError('name');
|
|
77
|
+
expect(state.isValid.value).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('clears all errors', () => {
|
|
81
|
+
const emit = vi.fn();
|
|
82
|
+
const state = useFormValidation({}, emit);
|
|
83
|
+
|
|
84
|
+
state.setFieldError('email', 'Invalid email');
|
|
85
|
+
state.setFieldError('name', 'Required');
|
|
86
|
+
|
|
87
|
+
state.clearErrors();
|
|
88
|
+
expect(state.errors.value).toEqual({});
|
|
89
|
+
expect(state.isValid.value).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('validate', () => {
|
|
94
|
+
it('returns true when there are no errors', () => {
|
|
95
|
+
const emit = vi.fn();
|
|
96
|
+
const state = useFormValidation({}, emit);
|
|
97
|
+
|
|
98
|
+
expect(state.validate()).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('clears errors before validation', () => {
|
|
102
|
+
const emit = vi.fn();
|
|
103
|
+
const state = useFormValidation({}, emit);
|
|
104
|
+
|
|
105
|
+
state.setFieldError('name', 'Error');
|
|
106
|
+
state.validate();
|
|
107
|
+
expect(state.errors.value).toEqual({});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('handleSubmit', () => {
|
|
112
|
+
it('prevents default form submission', async () => {
|
|
113
|
+
const emit = vi.fn();
|
|
114
|
+
const state = useFormValidation({}, emit);
|
|
115
|
+
const event = new Event('submit');
|
|
116
|
+
const preventDefaultSpy = vi.spyOn(event, 'preventDefault');
|
|
117
|
+
|
|
118
|
+
await state.handleSubmit(event);
|
|
119
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('emits submit event', async () => {
|
|
123
|
+
const emit = vi.fn();
|
|
124
|
+
const state = useFormValidation({}, emit);
|
|
125
|
+
const event = new Event('submit');
|
|
126
|
+
|
|
127
|
+
await state.handleSubmit(event);
|
|
128
|
+
expect(emit).toHaveBeenCalledWith('submit', event);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('executes callback with form data', async () => {
|
|
132
|
+
const emit = vi.fn();
|
|
133
|
+
const initialData = { name: 'John', email: 'john@test.com' };
|
|
134
|
+
const state = useFormValidation({ initialData }, emit);
|
|
135
|
+
const callback = vi.fn();
|
|
136
|
+
const event = new Event('submit');
|
|
137
|
+
|
|
138
|
+
await state.handleSubmit(event, callback);
|
|
139
|
+
expect(callback).toHaveBeenCalledWith(initialData);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('sets isSubmitting during submission', async () => {
|
|
143
|
+
const emit = vi.fn();
|
|
144
|
+
const state = useFormValidation({}, emit);
|
|
145
|
+
const callback = vi.fn(async () => {
|
|
146
|
+
expect(state.isSubmitting.value).toBe(true);
|
|
147
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
148
|
+
});
|
|
149
|
+
const event = new Event('submit');
|
|
150
|
+
|
|
151
|
+
await state.handleSubmit(event, callback);
|
|
152
|
+
expect(state.isSubmitting.value).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('handles async callback errors', async () => {
|
|
156
|
+
const emit = vi.fn();
|
|
157
|
+
const state = useFormValidation({}, emit);
|
|
158
|
+
const error = new Error('Submission failed');
|
|
159
|
+
const callback = vi.fn(async () => {
|
|
160
|
+
throw error;
|
|
161
|
+
});
|
|
162
|
+
const event = new Event('submit');
|
|
163
|
+
|
|
164
|
+
await expect(state.handleSubmit(event, callback)).rejects.toThrow(
|
|
165
|
+
'Submission failed'
|
|
166
|
+
);
|
|
167
|
+
expect(emit).toHaveBeenCalledWith('submit-error', error);
|
|
168
|
+
expect(state.isSubmitting.value).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('reset', () => {
|
|
173
|
+
it('resets form to initial state', () => {
|
|
174
|
+
const emit = vi.fn();
|
|
175
|
+
const initialData = { name: 'John', email: 'john@test.com' };
|
|
176
|
+
const state = useFormValidation({ initialData }, emit);
|
|
177
|
+
|
|
178
|
+
state.setFieldValue('name', 'Alice');
|
|
179
|
+
state.setFieldError('email', 'Invalid');
|
|
180
|
+
state.isSubmitting.value = true;
|
|
181
|
+
|
|
182
|
+
state.reset();
|
|
183
|
+
expect(state.formData.value).toEqual(initialData);
|
|
184
|
+
expect(state.errors.value).toEqual({});
|
|
185
|
+
expect(state.isValid.value).toBe(true);
|
|
186
|
+
expect(state.isSubmitting.value).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('resets to empty object when no initial data', () => {
|
|
190
|
+
const emit = vi.fn();
|
|
191
|
+
const state = useFormValidation({}, emit);
|
|
192
|
+
|
|
193
|
+
state.setFieldValue('name', 'Alice');
|
|
194
|
+
state.reset();
|
|
195
|
+
expect(state.formData.value).toEqual({});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|