sprintify-ui 0.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.
Files changed (176) hide show
  1. package/README.md +188 -0
  2. package/dist/types/src/components/BaseAlert.vue.d.ts +51 -0
  3. package/dist/types/src/components/BaseAutocomplete.vue.d.ts +268 -0
  4. package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +273 -0
  5. package/dist/types/src/components/BaseAvatar.vue.d.ts +126 -0
  6. package/dist/types/src/components/BaseBadge.vue.d.ts +94 -0
  7. package/dist/types/src/components/BaseBelongsTo.vue.d.ts +268 -0
  8. package/dist/types/src/components/BaseBoolean.vue.d.ts +64 -0
  9. package/dist/types/src/components/BaseBreadcrumbs.vue.d.ts +66 -0
  10. package/dist/types/src/components/BaseButton.vue.d.ts +23 -0
  11. package/dist/types/src/components/BaseCard.vue.d.ts +74 -0
  12. package/dist/types/src/components/BaseCardRow.vue.d.ts +16 -0
  13. package/dist/types/src/components/BaseClipboard.vue.d.ts +74 -0
  14. package/dist/types/src/components/BaseContainer.vue.d.ts +34 -0
  15. package/dist/types/src/components/BaseCounter.vue.d.ts +125 -0
  16. package/dist/types/src/components/BaseDataIterator.vue.d.ts +345 -0
  17. package/dist/types/src/components/BaseDataTable.vue.d.ts +657 -0
  18. package/dist/types/src/components/BaseDataTableToggleColumns.vue.d.ts +1281 -0
  19. package/dist/types/src/components/BaseDatePicker.vue.d.ts +190 -0
  20. package/dist/types/src/components/BaseDateSelect.vue.d.ts +171 -0
  21. package/dist/types/src/components/BaseDescriptionList.vue.d.ts +48 -0
  22. package/dist/types/src/components/BaseDescriptionListItem.vue.d.ts +49 -0
  23. package/dist/types/src/components/BaseDialog.vue.d.ts +160 -0
  24. package/dist/types/src/components/BaseFilePicker.vue.d.ts +44 -0
  25. package/dist/types/src/components/BaseFileUploader.vue.d.ts +220 -0
  26. package/dist/types/src/components/BaseInput.vue.d.ts +209 -0
  27. package/dist/types/src/components/BaseInputLabel.vue.d.ts +31 -0
  28. package/dist/types/src/components/BaseLoadingCover.vue.d.ts +166 -0
  29. package/dist/types/src/components/BaseLoadingPage.vue.d.ts +2 -0
  30. package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +269 -0
  31. package/dist/types/src/components/BaseMediaLibraryItem.vue.d.ts +75 -0
  32. package/dist/types/src/components/BaseMenu.vue.d.ts +117 -0
  33. package/dist/types/src/components/BaseMenuItem.vue.d.ts +147 -0
  34. package/dist/types/src/components/BaseModalCenter.vue.d.ts +141 -0
  35. package/dist/types/src/components/BaseModalSide.vue.d.ts +141 -0
  36. package/dist/types/src/components/BaseNavbar.vue.d.ts +79 -0
  37. package/dist/types/src/components/BaseNavbarItem.vue.d.ts +80 -0
  38. package/dist/types/src/components/BaseNavbarItemContent.vue.d.ts +127 -0
  39. package/dist/types/src/components/BasePagination.vue.d.ts +25 -0
  40. package/dist/types/src/components/BasePaginationSimple.vue.d.ts +25 -0
  41. package/dist/types/src/components/BasePanel.vue.d.ts +31 -0
  42. package/dist/types/src/components/BasePassword.vue.d.ts +66 -0
  43. package/dist/types/src/components/BaseProcessRing.vue.d.ts +36 -0
  44. package/dist/types/src/components/BaseReadMore.vue.d.ts +74 -0
  45. package/dist/types/src/components/BaseSelect.vue.d.ts +55 -0
  46. package/dist/types/src/components/BaseSideNavigation.vue.d.ts +48 -0
  47. package/dist/types/src/components/BaseSideNavigationItem.vue.d.ts +92 -0
  48. package/dist/types/src/components/BaseSkeleton.vue.d.ts +93 -0
  49. package/dist/types/src/components/BaseSpinner.vue.d.ts +2 -0
  50. package/dist/types/src/components/BaseSwitch.vue.d.ts +39 -0
  51. package/dist/types/src/components/BaseSystemAlert.vue.d.ts +141 -0
  52. package/dist/types/src/components/BaseTabItem.vue.d.ts +70 -0
  53. package/dist/types/src/components/BaseTable.vue.d.ts +467 -0
  54. package/dist/types/src/components/BaseTableColumn.vue.d.ts +164 -0
  55. package/dist/types/src/components/BaseTabs.vue.d.ts +48 -0
  56. package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +274 -0
  57. package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +251 -0
  58. package/dist/types/src/components/BaseTextarea.vue.d.ts +228 -0
  59. package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +44 -0
  60. package/dist/types/src/components/BaseTitle.vue.d.ts +45 -0
  61. package/dist/types/src/components/BaseWordCount.vue.d.ts +31 -0
  62. package/dist/types/src/components/SlotComponent.d.ts +43 -0
  63. package/dist/types/src/components/index.d.ts +2 -0
  64. package/dist/types/src/composables/breakpoints.d.ts +12 -0
  65. package/dist/types/src/composables/modal.d.ts +6 -0
  66. package/dist/types/src/constants/MyConstants.d.ts +1 -0
  67. package/dist/types/src/constants/index.d.ts +2 -0
  68. package/dist/types/src/index.d.ts +253 -0
  69. package/dist/types/src/types/Media.d.ts +8 -0
  70. package/dist/types/src/types/UploadedFile.d.ts +9 -0
  71. package/dist/types/src/types/User.d.ts +6 -0
  72. package/dist/types/src/types/types.d.ts +88 -0
  73. package/dist/types/src/utils/fileSizeFormat.d.ts +1 -0
  74. package/dist/types/src/utils/index.d.ts +4 -0
  75. package/dist/types/src/utils/scrollPreventer.d.ts +4 -0
  76. package/dist/types/src/utils/toHumanList.d.ts +1 -0
  77. package/package.json +99 -0
  78. package/src/assets/button.css +80 -0
  79. package/src/assets/form.css +15 -0
  80. package/src/assets/main.css +3 -0
  81. package/src/assets/pikaday.css +134 -0
  82. package/src/assets/tailwind.css +5 -0
  83. package/src/components/BaseAlert.stories.js +52 -0
  84. package/src/components/BaseAlert.vue +152 -0
  85. package/src/components/BaseAutocomplete.stories.js +127 -0
  86. package/src/components/BaseAutocomplete.vue +376 -0
  87. package/src/components/BaseAutocompleteFetch.stories.js +121 -0
  88. package/src/components/BaseAutocompleteFetch.vue +185 -0
  89. package/src/components/BaseAvatar.stories.js +39 -0
  90. package/src/components/BaseAvatar.vue +92 -0
  91. package/src/components/BaseBadge.stories.js +61 -0
  92. package/src/components/BaseBadge.vue +70 -0
  93. package/src/components/BaseBelongsTo.stories.js +130 -0
  94. package/src/components/BaseBelongsTo.vue +122 -0
  95. package/src/components/BaseBoolean.stories.js +35 -0
  96. package/src/components/BaseBoolean.vue +29 -0
  97. package/src/components/BaseBreadcrumbs.stories.js +45 -0
  98. package/src/components/BaseBreadcrumbs.vue +78 -0
  99. package/src/components/BaseButton.stories.js +80 -0
  100. package/src/components/BaseButton.vue +39 -0
  101. package/src/components/BaseCard.stories.js +61 -0
  102. package/src/components/BaseCard.vue +49 -0
  103. package/src/components/BaseCardRow.vue +34 -0
  104. package/src/components/BaseClipboard.stories.js +31 -0
  105. package/src/components/BaseClipboard.vue +96 -0
  106. package/src/components/BaseContainer.stories.js +34 -0
  107. package/src/components/BaseContainer.vue +50 -0
  108. package/src/components/BaseCounter.stories.js +32 -0
  109. package/src/components/BaseCounter.vue +72 -0
  110. package/src/components/BaseDataIterator.stories.js +90 -0
  111. package/src/components/BaseDataIterator.vue +658 -0
  112. package/src/components/BaseDataTable.stories.js +95 -0
  113. package/src/components/BaseDataTable.vue +489 -0
  114. package/src/components/BaseDataTableToggleColumns.vue +69 -0
  115. package/src/components/BaseDatePicker.stories.js +53 -0
  116. package/src/components/BaseDatePicker.vue +166 -0
  117. package/src/components/BaseDateSelect.vue +192 -0
  118. package/src/components/BaseDescriptionList.vue +11 -0
  119. package/src/components/BaseDescriptionListItem.vue +12 -0
  120. package/src/components/BaseDialog.vue +104 -0
  121. package/src/components/BaseFilePicker.vue +101 -0
  122. package/src/components/BaseFileUploader.vue +166 -0
  123. package/src/components/BaseInput.vue +82 -0
  124. package/src/components/BaseInputLabel.vue +26 -0
  125. package/src/components/BaseLoadingCover.vue +84 -0
  126. package/src/components/BaseLoadingPage.vue +19 -0
  127. package/src/components/BaseMediaLibrary.vue +281 -0
  128. package/src/components/BaseMediaLibraryItem.vue +92 -0
  129. package/src/components/BaseMenu.vue +114 -0
  130. package/src/components/BaseMenuItem.vue +93 -0
  131. package/src/components/BaseModalCenter.vue +107 -0
  132. package/src/components/BaseModalSide.vue +112 -0
  133. package/src/components/BaseNavbar.vue +72 -0
  134. package/src/components/BaseNavbarItem.vue +72 -0
  135. package/src/components/BaseNavbarItemContent.vue +57 -0
  136. package/src/components/BasePagination.vue +82 -0
  137. package/src/components/BasePaginationSimple.vue +60 -0
  138. package/src/components/BasePanel.vue +39 -0
  139. package/src/components/BasePassword.vue +73 -0
  140. package/src/components/BaseProcessRing.vue +56 -0
  141. package/src/components/BaseReadMore.vue +72 -0
  142. package/src/components/BaseSelect.vue +59 -0
  143. package/src/components/BaseSideNavigation.vue +7 -0
  144. package/src/components/BaseSideNavigationItem.vue +42 -0
  145. package/src/components/BaseSkeleton.vue +24 -0
  146. package/src/components/BaseSpinner.vue +47 -0
  147. package/src/components/BaseSwitch.vue +87 -0
  148. package/src/components/BaseSystemAlert.vue +86 -0
  149. package/src/components/BaseTabItem.vue +30 -0
  150. package/src/components/BaseTable.vue +781 -0
  151. package/src/components/BaseTableColumn.vue +109 -0
  152. package/src/components/BaseTabs.vue +12 -0
  153. package/src/components/BaseTagAutocomplete.vue +385 -0
  154. package/src/components/BaseTagAutocompleteFetch.vue +154 -0
  155. package/src/components/BaseTextarea.vue +73 -0
  156. package/src/components/BaseTextareaAutoresize.vue +117 -0
  157. package/src/components/BaseTitle.vue +80 -0
  158. package/src/components/BaseWordCount.vue +36 -0
  159. package/src/components/SlotComponent.ts +37 -0
  160. package/src/components/index.ts +5 -0
  161. package/src/composables/breakpoints.ts +6 -0
  162. package/src/composables/modal.ts +77 -0
  163. package/src/constants/MyConstants.ts +1 -0
  164. package/src/constants/index.ts +5 -0
  165. package/src/env.d.ts +15 -0
  166. package/src/index.ts +70 -0
  167. package/src/lang/en.json +56 -0
  168. package/src/lang/fr.json +56 -0
  169. package/src/types/Media.ts +9 -0
  170. package/src/types/UploadedFile.ts +10 -0
  171. package/src/types/User.ts +7 -0
  172. package/src/types/types.ts +112 -0
  173. package/src/utils/fileSizeFormat.ts +15 -0
  174. package/src/utils/index.ts +5 -0
  175. package/src/utils/scrollPreventer.ts +21 -0
  176. package/src/utils/toHumanList.ts +20 -0
@@ -0,0 +1,658 @@
1
+ <template>
2
+ <div ref="dataIteratorNode">
3
+ <div
4
+ class="grid w-full max-w-full grid-flow-row gap-4"
5
+ :class="{
6
+ 'grid-cols-[1fr_300px]': hasSidebar,
7
+ }"
8
+ >
9
+ <div class="col-span-2 min-w-0 lg:col-span-1">
10
+ <!-- Header -->
11
+ <div class="mb-4 flex space-x-2 empty:mb-0">
12
+ <!-- Search bar -->
13
+ <div v-if="searchable" class="grow">
14
+ <div class="relative h-11">
15
+ <div
16
+ class="pointer-events-none absolute top-0 left-0 flex h-full items-center justify-center pl-2.5"
17
+ >
18
+ <Icon
19
+ class="h-6 w-6 text-slate-400"
20
+ icon="heroicons:magnifying-glass"
21
+ />
22
+ </div>
23
+ <input
24
+ ref="searchInput"
25
+ v-model="searchQuery"
26
+ type="text"
27
+ class="h-11 w-full overflow-hidden rounded-md border border-slate-300 bg-white pl-10 pr-9 shadow-sm"
28
+ :placeholder="$t('sui.autocomplete_placeholder')"
29
+ @input="onSearch"
30
+ />
31
+ <div
32
+ v-if="searchQuery"
33
+ class="absolute top-0 right-0 flex h-full items-center justify-center p-1"
34
+ >
35
+ <button
36
+ type="button"
37
+ class="flex appearance-none items-center rounded p-1 hover:bg-slate-100"
38
+ @click="
39
+ searchQuery = '';
40
+ onSearch('');
41
+ "
42
+ >
43
+ <Icon
44
+ class="h-6 w-6 text-slate-500"
45
+ icon="heroicons:x-mark"
46
+ />
47
+ </button>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- Filters (mobile) -->
53
+ <button
54
+ v-if="mobileLayout && hasFilters"
55
+ class="btn flex h-11 items-center justify-center py-1 text-base"
56
+ type="button"
57
+ @click="showFilters = true"
58
+ >
59
+ <Icon
60
+ class="mr-2 h-6 w-6 text-slate-500"
61
+ icon="heroicons:adjustments-horizontal-solid"
62
+ />
63
+ <span>{{ $t('sui.filters') }}</span>
64
+ </button>
65
+
66
+ <!-- Menu -->
67
+ <BaseMenu
68
+ v-if="actions && actions.length"
69
+ menu-class="w-52"
70
+ :items="actions"
71
+ >
72
+ <template #button="{ open }">
73
+ <div
74
+ class="flex h-11 w-11 items-center justify-center rounded-md border border-slate-300 bg-white shadow-sm duration-150 hover:bg-slate-50"
75
+ :class="[
76
+ open ? 'ring-2 ring-primary-500 ring-offset-2' : false,
77
+ ]"
78
+ >
79
+ <Icon icon="heroicons-solid:dots-vertical" />
80
+ </div>
81
+ </template>
82
+ </BaseMenu>
83
+ </div>
84
+
85
+ <slot
86
+ :items="items"
87
+ :loading="loading"
88
+ :error="error"
89
+ :first-load="firstLoad"
90
+ :page="page"
91
+ :sort-field="sortField"
92
+ :sort-direction="sortDirection"
93
+ :on-sort-change="onSortChange"
94
+ :on-page-change="onPageChange"
95
+ />
96
+
97
+ <!-- Pagination -->
98
+
99
+ <div class="mt-4">
100
+ <div
101
+ class="flex flex-wrap xs:flex-nowrap xs:items-center xs:justify-between"
102
+ >
103
+ <div class="flex items-center space-x-3">
104
+ <BasePaginationSimple
105
+ :model-value="page"
106
+ :last-page="lastPage"
107
+ @model-value:update="onPageChange"
108
+ />
109
+ <div>
110
+ <p class="text-sm text-slate-600">
111
+ {{
112
+ (paginationMetadata.current_page - 1) *
113
+ paginationMetadata.per_page +
114
+ 1
115
+ }}
116
+ -
117
+ {{
118
+ $t('sui.pagination_detail', {
119
+ page: Math.min(
120
+ paginationMetadata.current_page *
121
+ paginationMetadata.per_page,
122
+ paginationMetadata.total
123
+ ),
124
+ total: paginationMetadata.total,
125
+ })
126
+ }}
127
+ </p>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+
134
+ <div v-if="!mobileLayout && hasSidebar">
135
+ <slot
136
+ name="sidebarTop"
137
+ :pagination-metadata="paginationMetadata"
138
+ ></slot>
139
+
140
+ <div v-if="hasFilters" class="mb-4">
141
+ <BaseCard>
142
+ <BaseCardRow size="sm">
143
+ <div class="space-y-3">
144
+ <slot
145
+ name="filters"
146
+ :query="query"
147
+ :update-query="updateFilterQuery"
148
+ :update-query-value="updateFilterQueryValue"
149
+ />
150
+ </div>
151
+ </BaseCardRow>
152
+ </BaseCard>
153
+ </div>
154
+
155
+ <slot
156
+ name="sidebarBottom"
157
+ :pagination-metadata="paginationMetadata"
158
+ ></slot>
159
+ </div>
160
+ </div>
161
+
162
+ <BaseModalSide v-if="hasFilters" v-model="showFilters">
163
+ <div class="px-4 py-5">
164
+ <h2 class="mb-4 font-semibold">
165
+ {{ $t('sui.filters') }}
166
+ </h2>
167
+
168
+ <div class="space-y-3">
169
+ <slot
170
+ name="filters"
171
+ :query="query"
172
+ :update-query="updateFilterQuery"
173
+ :update-query-value="updateFilterQueryValue"
174
+ />
175
+ </div>
176
+
177
+ <div>
178
+ <button class="btn btn-primary mt-4" @click="showFilters = false">
179
+ {{ $t('sui.apply_filters') }}
180
+ </button>
181
+ </div>
182
+ </div>
183
+ </BaseModalSide>
184
+ </div>
185
+ </template>
186
+
187
+ <script lang="ts">
188
+ /* eslint-disable @typescript-eslint/no-explicit-any */
189
+
190
+ type Direction = 'asc' | 'desc';
191
+
192
+ const DEFAULT_QUERY = {
193
+ page: 1,
194
+ search: '',
195
+ sort: '',
196
+ filter: {},
197
+ } as DataTableQuery;
198
+ </script>
199
+
200
+ <script lang="ts" setup>
201
+ import { cloneDeep, debounce, isArray, merge, set } from 'lodash';
202
+ import hash from 'object-hash';
203
+ import { ComputedRef, PropType, Ref } from 'vue';
204
+ import { useBreakpoints } from '@/composables/breakpoints';
205
+ import {
206
+ Collection,
207
+ DataTableQuery,
208
+ MenuItemInterface,
209
+ PaginatedCollection,
210
+ PaginationMetadata,
211
+ ResourceCollection,
212
+ } from '@/types/types';
213
+
214
+ import BaseMenu from './BaseMenu.vue';
215
+ import BaseCard from './BaseCard.vue';
216
+ import BaseCardRow from './BaseCardRow.vue';
217
+ import BasePaginationSimple from './BasePaginationSimple.vue';
218
+ import BaseModalSide from './BaseModalSide.vue';
219
+ import { config } from 'src';
220
+
221
+ const props = defineProps({
222
+ /**
223
+ * Base URL from which to make requests
224
+ */
225
+ url: {
226
+ required: true,
227
+ type: String,
228
+ },
229
+
230
+ /**
231
+ * Route key name for Laravel route model binding
232
+ */
233
+ routeKeyName: {
234
+ default: 'id',
235
+ type: String,
236
+ },
237
+
238
+ /**
239
+ * Query params that always get applied.
240
+ * To add overwrite-able query params, use defaultQuery.
241
+ */
242
+ urlQuery: {
243
+ default: undefined,
244
+ type: Object as PropType<Record<string, any>>,
245
+ },
246
+
247
+ /**
248
+ * Query params that gets applied by default.
249
+ * These may be overwritten by URL params generated by the data-table or filters
250
+ * To add query params that are always active, use urlQuery.
251
+ */
252
+ defaultQuery: {
253
+ default: function () {
254
+ return DEFAULT_QUERY;
255
+ },
256
+ type: Object as PropType<DataTableQuery>,
257
+ },
258
+
259
+ /**
260
+ * Add a search bar.
261
+ */
262
+ searchable: {
263
+ default: true,
264
+ type: Boolean,
265
+ },
266
+
267
+ /**
268
+ * Configure contextual actions.
269
+ */
270
+ actions: {
271
+ default: undefined,
272
+ type: Array as PropType<MenuItemInterface[]>,
273
+ },
274
+
275
+ /**
276
+ * Save data table state in URL.
277
+ */
278
+ historyMode: {
279
+ default: true,
280
+ type: Boolean,
281
+ },
282
+ });
283
+
284
+ const http = config.http;
285
+
286
+ defineEmits([
287
+ 'click',
288
+ 'delete',
289
+ 'checkAll',
290
+ 'update:checked-rows',
291
+ 'check',
292
+ 'update:query',
293
+ ]);
294
+
295
+ const dataIteratorNode = ref(null) as Ref<null | HTMLElement>;
296
+ const searchInput = ref(null) as Ref<null | HTMLInputElement>;
297
+
298
+ const route = useRoute();
299
+ const router = useRouter();
300
+ const routeName = route.name;
301
+
302
+ const breakpoints = useBreakpoints();
303
+
304
+ /** Data table state */
305
+
306
+ const firstLoad = ref(false);
307
+ const loading = ref(true);
308
+ const error = ref(false);
309
+ const showFilters = ref(false);
310
+ const searchQuery = ref('');
311
+
312
+ let lastUrl = '';
313
+ let lastQueryHash = '';
314
+ const query = ref(cloneDeep(props.defaultQuery)) as Ref<DataTableQuery>;
315
+ const slots = useSlots();
316
+
317
+ const mobileLayout = computed(() => {
318
+ return breakpoints.smaller('lg').value;
319
+ });
320
+
321
+ const hasFilters = computed((): boolean => {
322
+ const numberOfFilterSlots = slots.filters;
323
+ return numberOfFilterSlots !== undefined;
324
+ });
325
+
326
+ const hasSidebar = computed(() => {
327
+ return (
328
+ hasFilters.value ||
329
+ slots.sidebarTop !== undefined ||
330
+ slots.sidebarBottom !== undefined
331
+ );
332
+ });
333
+
334
+ /*
335
+ |--------------------------------------------------------------------------
336
+ | Query params
337
+ |--------------------------------------------------------------------------
338
+ */
339
+
340
+ function updateFilterQueryValue(key: string, value: any) {
341
+ let newQuery = cloneDeep(query.value);
342
+ newQuery = set(newQuery, key, value);
343
+ newQuery = set(newQuery, 'page', 1);
344
+ updateQuery(newQuery);
345
+ }
346
+
347
+ function updateFilterQuery(newQuery: DataTableQuery) {
348
+ newQuery = set(newQuery, 'page', 1);
349
+ updateQuery(newQuery);
350
+ }
351
+
352
+ function updateQuery(newQuery: DataTableQuery) {
353
+ if (!props.historyMode) {
354
+ query.value = newQuery;
355
+ fetch();
356
+ return;
357
+ }
358
+
359
+ const newRoute = router.resolve({
360
+ path: route.path,
361
+ params: route.params,
362
+ });
363
+
364
+ const newParams = config.formatQueryString(newQuery);
365
+ const newRoutePath = newRoute.fullPath + '?' + newParams;
366
+
367
+ const oldParamString = getRouteQuery();
368
+ const oldParams = config.formatQueryString(oldParamString);
369
+
370
+ // Push new route if different
371
+ if (oldParams != newParams) {
372
+ if (!firstLoad.value) {
373
+ router.replace(newRoutePath);
374
+ return;
375
+ }
376
+ router.push(newRoutePath);
377
+ return;
378
+ }
379
+
380
+ // If the URL is unchanged, we must manually trigger the fetch() method
381
+ // on first load.
382
+
383
+ if (!firstLoad.value) {
384
+ query.value = newQuery;
385
+ fetch();
386
+ }
387
+ }
388
+
389
+ function queryHash(query: DataTableQuery): string {
390
+ return hash(query);
391
+ }
392
+
393
+ /*
394
+ |--------------------------------------------------------------------------
395
+ | Data fetching
396
+ |--------------------------------------------------------------------------
397
+ */
398
+
399
+ const url = computed(() => {
400
+ return props.url;
401
+ }) as ComputedRef<string>;
402
+
403
+ /*
404
+ |--------------------------------------------------------------------------
405
+ | Handlers
406
+ |--------------------------------------------------------------------------
407
+ */
408
+
409
+ function onPageChange(p: number) {
410
+ const newQuery = cloneDeep(query.value);
411
+
412
+ newQuery.page = p;
413
+
414
+ updateQuery(newQuery);
415
+
416
+ scrollIntoView();
417
+ }
418
+
419
+ function onSortChange(field: string, direction: Direction) {
420
+ let newSort = field;
421
+
422
+ if (newSort && direction == 'desc') {
423
+ newSort = '-' + newSort;
424
+ }
425
+
426
+ const newQuery = cloneDeep(query.value);
427
+
428
+ newQuery.page = 1;
429
+ newQuery.sort = newSort;
430
+
431
+ updateQuery(newQuery);
432
+ }
433
+
434
+ const onSearch = debounce((event: any) => {
435
+ const newQuery = cloneDeep(query.value);
436
+
437
+ newQuery.page = 1;
438
+ newQuery.search = searchQuery.value;
439
+
440
+ updateQuery(newQuery);
441
+ }, 350);
442
+
443
+ /*
444
+ |--------------------------------------------------------------------------
445
+ | Route watcher
446
+ |--------------------------------------------------------------------------
447
+ */
448
+
449
+ watch(
450
+ () => route.query,
451
+ () => {
452
+ onRouteChange();
453
+ }
454
+ );
455
+
456
+ function getRouteQuery() {
457
+ return config.parseQueryString(window.location.search.replace(/^(\?)/, ''));
458
+ }
459
+
460
+ function onRouteChange() {
461
+ if (!props.historyMode) {
462
+ return;
463
+ }
464
+
465
+ // Stop if route was changed
466
+ if (route.name != routeName) {
467
+ return;
468
+ }
469
+
470
+ const routeQuery = getRouteQuery();
471
+ const newQuery = routeQuery as DataTableQuery;
472
+
473
+ const newQueryHash = queryHash(newQuery);
474
+
475
+ if (newQueryHash == lastQueryHash) {
476
+ return;
477
+ }
478
+
479
+ lastQueryHash = newQueryHash;
480
+
481
+ query.value = newQuery;
482
+
483
+ // Update search input if not in focus
484
+ if (searchInput.value && searchInput.value !== document.activeElement) {
485
+ updateSearchInput();
486
+ }
487
+
488
+ fetch();
489
+ }
490
+
491
+ /*
492
+ |--------------------------------------------------------------------------
493
+ | Fetch
494
+ |--------------------------------------------------------------------------
495
+ */
496
+
497
+ function fetch(force = false) {
498
+ const urlSplit = url.value.split(/[?#]/);
499
+
500
+ const baseUrl = urlSplit[0];
501
+ const urlQueryString = urlSplit[1] ?? null;
502
+ const urlQuery = config.parseQueryString(urlQueryString);
503
+
504
+ const allParams = merge(
505
+ cloneDeep(query.value),
506
+ cloneDeep(props.urlQuery),
507
+ cloneDeep(urlQuery)
508
+ );
509
+
510
+ const queryString = config.formatQueryString(allParams);
511
+
512
+ const fullUrl = baseUrl + '?' + queryString;
513
+
514
+ if (lastUrl == fullUrl && !force) {
515
+ return;
516
+ }
517
+
518
+ loading.value = true;
519
+ lastUrl = fullUrl;
520
+
521
+ http
522
+ .get(fullUrl)
523
+ .then((response) => {
524
+ data.value = response.data;
525
+ loading.value = false;
526
+ error.value = false;
527
+ firstLoad.value = true;
528
+ })
529
+ .catch(() => {
530
+ loading.value = false;
531
+ error.value = true;
532
+ });
533
+ }
534
+
535
+ /*
536
+ |--------------------------------------------------------------------------
537
+ | Data parsing
538
+ |--------------------------------------------------------------------------
539
+ */
540
+
541
+ const data = ref(null) as Ref<
542
+ null | ResourceCollection | PaginatedCollection | Collection
543
+ >;
544
+
545
+ const items = computed(() => {
546
+ if (!data.value) {
547
+ return [];
548
+ }
549
+ if (isArray(data.value)) {
550
+ return data.value;
551
+ }
552
+ return data.value.data;
553
+ });
554
+
555
+ const paginationMetadata = computed(() => {
556
+ const defaultMeta = {
557
+ current_page: 1,
558
+ last_page: 1,
559
+ per_page: 1,
560
+ total: 0,
561
+ };
562
+
563
+ if (!data.value) {
564
+ return defaultMeta;
565
+ }
566
+ if (isArray(data.value)) {
567
+ return defaultMeta;
568
+ }
569
+ if ('meta' in data.value) {
570
+ return data.value.meta;
571
+ }
572
+ return {
573
+ current_page: data.value.current_page,
574
+ last_page: data.value.last_page,
575
+ per_page: data.value.per_page,
576
+ total: data.value.total,
577
+ };
578
+ }) as ComputedRef<PaginationMetadata>;
579
+
580
+ const page = computed((): number => {
581
+ if (query.value.page && parseInt(query.value.page + '')) {
582
+ return parseInt(query.value.page + '');
583
+ }
584
+ return 1;
585
+ });
586
+
587
+ const lastPage = computed(() => {
588
+ return paginationMetadata.value.last_page;
589
+ });
590
+
591
+ const sortField = computed((): string => {
592
+ return query.value.sort?.trim().replace(/^(-)/, '') ?? '';
593
+ });
594
+
595
+ const sortDirection = computed((): Direction => {
596
+ if (query.value.sort && query.value.sort.length) {
597
+ if (query.value.sort[0] == '-') {
598
+ return 'desc';
599
+ }
600
+ }
601
+ return 'asc';
602
+ });
603
+
604
+ const searchKeywords = computed((): string => {
605
+ return query.value.search ?? '';
606
+ });
607
+
608
+ /*
609
+ |--------------------------------------------------------------------------
610
+ | Helpers
611
+ |--------------------------------------------------------------------------
612
+ */
613
+
614
+ /** Scroll into view */
615
+
616
+ const scrollIntoView = () => {
617
+ if (dataIteratorNode.value == null) {
618
+ return;
619
+ }
620
+ dataIteratorNode.value.scrollIntoView({
621
+ behavior: 'smooth',
622
+ });
623
+ };
624
+
625
+ function updateSearchInput() {
626
+ searchQuery.value = searchKeywords.value;
627
+ }
628
+
629
+ /*
630
+ |--------------------------------------------------------------------------
631
+ | Created
632
+ |--------------------------------------------------------------------------
633
+ */
634
+
635
+ let newQuery = cloneDeep(query.value);
636
+ const routeQuery = getRouteQuery();
637
+
638
+ newQuery = merge(newQuery, routeQuery);
639
+
640
+ updateQuery(newQuery);
641
+
642
+ // Update search input on first load
643
+ onMounted(() => {
644
+ updateSearchInput();
645
+ });
646
+
647
+ /*
648
+ |--------------------------------------------------------------------------
649
+ | Exposed API
650
+ |--------------------------------------------------------------------------
651
+ */
652
+
653
+ defineExpose({
654
+ fetch,
655
+ scrollIntoView,
656
+ query,
657
+ });
658
+ </script>