adminforth 2.4.0-next.21 → 2.4.0-next.210

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 (192) hide show
  1. package/commands/callTsProxy.js +14 -4
  2. package/commands/cli.js +10 -3
  3. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  4. package/commands/createApp/templates/index.ts.hbs +10 -2
  5. package/commands/createApp/templates/package.json.hbs +1 -1
  6. package/commands/createApp/utils.js +27 -2
  7. package/commands/createCustomComponent/configLoader.js +3 -0
  8. package/commands/createCustomComponent/main.js +1 -0
  9. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  10. package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
  11. package/commands/createPlugin/templates/package.json.hbs +1 -1
  12. package/dist/auth.d.ts +9 -1
  13. package/dist/auth.d.ts.map +1 -1
  14. package/dist/auth.js +15 -2
  15. package/dist/auth.js.map +1 -1
  16. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  17. package/dist/dataConnectors/baseConnector.js +46 -15
  18. package/dist/dataConnectors/baseConnector.js.map +1 -1
  19. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  20. package/dist/dataConnectors/clickhouse.js +15 -0
  21. package/dist/dataConnectors/clickhouse.js.map +1 -1
  22. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  23. package/dist/dataConnectors/mongo.js +44 -15
  24. package/dist/dataConnectors/mongo.js.map +1 -1
  25. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  26. package/dist/dataConnectors/mysql.js +11 -0
  27. package/dist/dataConnectors/mysql.js.map +1 -1
  28. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  29. package/dist/dataConnectors/postgres.js +11 -0
  30. package/dist/dataConnectors/postgres.js.map +1 -1
  31. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  32. package/dist/dataConnectors/sqlite.js +11 -0
  33. package/dist/dataConnectors/sqlite.js.map +1 -1
  34. package/dist/index.d.ts +2 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +20 -9
  37. package/dist/index.js.map +1 -1
  38. package/dist/modules/codeInjector.d.ts +2 -0
  39. package/dist/modules/codeInjector.d.ts.map +1 -1
  40. package/dist/modules/codeInjector.js +52 -8
  41. package/dist/modules/codeInjector.js.map +1 -1
  42. package/dist/modules/configValidator.d.ts.map +1 -1
  43. package/dist/modules/configValidator.js +74 -7
  44. package/dist/modules/configValidator.js.map +1 -1
  45. package/dist/modules/restApi.d.ts.map +1 -1
  46. package/dist/modules/restApi.js +154 -26
  47. package/dist/modules/restApi.js.map +1 -1
  48. package/dist/modules/styles.d.ts +503 -13
  49. package/dist/modules/styles.d.ts.map +1 -1
  50. package/dist/modules/styles.js +559 -31
  51. package/dist/modules/styles.js.map +1 -1
  52. package/dist/modules/utils.d.ts +2 -0
  53. package/dist/modules/utils.d.ts.map +1 -1
  54. package/dist/modules/utils.js +16 -0
  55. package/dist/modules/utils.js.map +1 -1
  56. package/dist/servers/express.d.ts.map +1 -1
  57. package/dist/servers/express.js +14 -0
  58. package/dist/servers/express.js.map +1 -1
  59. package/dist/spa/index.html +1 -1
  60. package/dist/spa/package-lock.json +5 -4
  61. package/dist/spa/package.json +1 -1
  62. package/dist/spa/src/App.vue +54 -169
  63. package/dist/spa/src/adminforth.ts +42 -18
  64. package/dist/spa/src/afcl/BarChart.vue +2 -2
  65. package/dist/spa/src/afcl/Button.vue +6 -6
  66. package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
  67. package/dist/spa/src/afcl/Card.vue +25 -0
  68. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  69. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  70. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  71. package/dist/spa/src/afcl/Dialog.vue +44 -27
  72. package/dist/spa/src/afcl/Dropzone.vue +12 -12
  73. package/dist/spa/src/afcl/Input.vue +6 -6
  74. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  75. package/dist/spa/src/afcl/LinkButton.vue +3 -3
  76. package/dist/spa/src/afcl/PieChart.vue +5 -5
  77. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  78. package/dist/spa/src/afcl/Select.vue +68 -34
  79. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  80. package/dist/spa/src/afcl/Table.vue +213 -74
  81. package/dist/spa/src/afcl/Textarea.vue +31 -0
  82. package/dist/spa/src/afcl/Toggle.vue +32 -0
  83. package/dist/spa/src/afcl/Tooltip.vue +1 -2
  84. package/dist/spa/src/afcl/VerticalTabs.vue +16 -7
  85. package/dist/spa/src/afcl/index.ts +6 -3
  86. package/dist/spa/src/components/AcceptModal.vue +7 -7
  87. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  88. package/dist/spa/src/components/ColumnValueInput.vue +38 -18
  89. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  90. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  91. package/dist/spa/src/components/CustomRangePicker.vue +37 -8
  92. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  93. package/dist/spa/src/components/Filters.vue +85 -39
  94. package/dist/spa/src/components/GroupsTable.vue +9 -8
  95. package/dist/spa/src/components/MenuLink.vue +90 -23
  96. package/dist/spa/src/components/ResourceForm.vue +94 -51
  97. package/dist/spa/src/components/ResourceListTable.vue +78 -80
  98. package/dist/spa/src/components/ResourceListTableVirtual.vue +71 -73
  99. package/dist/spa/src/components/ShowTable.vue +17 -12
  100. package/dist/spa/src/components/Sidebar.vue +448 -0
  101. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  102. package/dist/spa/src/components/SkeleteLoader.vue +3 -3
  103. package/dist/spa/src/components/ThreeDotsMenu.vue +73 -14
  104. package/dist/spa/src/components/Toast.vue +27 -9
  105. package/dist/spa/src/components/UserMenuSettingsButton.vue +70 -0
  106. package/dist/spa/src/components/ValueRenderer.vue +43 -16
  107. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  108. package/dist/spa/src/i18n.ts +1 -1
  109. package/dist/spa/src/renderers/CompactField.vue +1 -1
  110. package/dist/spa/src/renderers/CompactUUID.vue +1 -1
  111. package/dist/spa/src/router/index.ts +8 -0
  112. package/dist/spa/src/shims-vue.d.ts +5 -0
  113. package/dist/spa/src/spa_types/core.ts +13 -1
  114. package/dist/spa/src/stores/core.ts +1 -1
  115. package/dist/spa/src/stores/filters.ts +29 -2
  116. package/dist/spa/src/stores/modal.ts +6 -1
  117. package/dist/spa/src/stores/toast.ts +22 -3
  118. package/dist/spa/src/types/Back.ts +137 -22
  119. package/dist/spa/src/types/Common.ts +67 -32
  120. package/dist/spa/src/types/FrontendAPI.ts +31 -5
  121. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  122. package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
  123. package/dist/spa/src/types/adapters/EmailAdapter.ts +27 -0
  124. package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
  125. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  126. package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
  127. package/dist/spa/src/types/adapters/OAuth2Adapter.ts +34 -0
  128. package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
  129. package/dist/spa/src/types/adapters/index.ts +8 -0
  130. package/dist/spa/src/utils.ts +219 -8
  131. package/dist/spa/src/views/CreateView.vue +18 -19
  132. package/dist/spa/src/views/EditView.vue +25 -19
  133. package/dist/spa/src/views/ListView.vue +139 -86
  134. package/dist/spa/src/views/LoginView.vue +31 -37
  135. package/dist/spa/src/views/ResourceParent.vue +2 -2
  136. package/dist/spa/src/views/SettingsView.vue +121 -0
  137. package/dist/spa/src/views/ShowView.vue +59 -39
  138. package/dist/spa/src/websocket.ts +6 -1
  139. package/dist/spa/tsconfig.app.json +1 -1
  140. package/dist/spa/vite.config.ts +45 -2
  141. package/dist/types/Back.d.ts +115 -14
  142. package/dist/types/Back.d.ts.map +1 -1
  143. package/dist/types/Back.js +15 -0
  144. package/dist/types/Back.js.map +1 -1
  145. package/dist/types/Common.d.ts +59 -29
  146. package/dist/types/Common.d.ts.map +1 -1
  147. package/dist/types/Common.js.map +1 -1
  148. package/dist/types/FrontendAPI.d.ts +31 -3
  149. package/dist/types/FrontendAPI.d.ts.map +1 -1
  150. package/dist/types/FrontendAPI.js.map +1 -1
  151. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  152. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  153. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  154. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  155. package/dist/types/adapters/CompletionAdapter.d.ts +20 -0
  156. package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
  157. package/dist/types/adapters/CompletionAdapter.js +2 -0
  158. package/dist/types/adapters/CompletionAdapter.js.map +1 -0
  159. package/dist/types/adapters/EmailAdapter.d.ts +20 -0
  160. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
  161. package/dist/types/adapters/EmailAdapter.js +2 -0
  162. package/dist/types/adapters/EmailAdapter.js.map +1 -0
  163. package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
  164. package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
  165. package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
  166. package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
  167. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  168. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  169. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  170. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  171. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  172. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  173. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  174. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  175. package/dist/types/adapters/OAuth2Adapter.d.ts +32 -0
  176. package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
  177. package/dist/types/adapters/OAuth2Adapter.js +2 -0
  178. package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
  179. package/dist/types/adapters/StorageAdapter.d.ts +63 -0
  180. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
  181. package/dist/types/adapters/StorageAdapter.js +2 -0
  182. package/dist/types/adapters/StorageAdapter.js.map +1 -0
  183. package/dist/types/adapters/index.d.ts +9 -0
  184. package/dist/types/adapters/index.d.ts.map +1 -0
  185. package/dist/types/adapters/index.js +2 -0
  186. package/dist/types/adapters/index.js.map +1 -0
  187. package/package.json +3 -2
  188. package/dist/spa/src/types/Adapters.ts +0 -213
  189. package/dist/types/Adapters.d.ts +0 -168
  190. package/dist/types/Adapters.d.ts.map +0 -1
  191. package/dist/types/Adapters.js +0 -2
  192. package/dist/types/Adapters.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  <template>
2
2
 
3
- <div class="afcl-input flex z-0 relative" :class="{'opacity-50' : readonly}">
3
+ <div class="afcl-input-wrapper flex z-0 relative" :class="{'opacity-50' : readonly}">
4
4
  <span
5
5
  v-if="$slots.prefix || prefix"
6
- class="inline-flex items-center px-3 text-sm text-gray-900 bg-gray-200 border border-s-0 border-gray-300 rounded-s-md dark:bg-gray-600 dark:text-gray-400 dark:border-gray-600">
6
+ class="inline-flex items-center px-3 text-sm text-lightInputText bg-lightInputBackground border border-s-0 border-lightInputBorder rounded-s-md dark:bg-darkInputBackground dark:text-darkInputText dark:border-darkInputBorder">
7
7
  <slot name="prefix">{{ prefix }}</slot>
8
8
  </span>
9
9
 
@@ -12,11 +12,11 @@
12
12
  ref="input"
13
13
  v-bind="$attrs"
14
14
  :type="type"
15
- @input="$emit('update:modelValue', $event.target?.value)"
15
+ @input="$emit('update:modelValue', type === 'number' ? Number(($event.target as HTMLInputElement)?.value) : ($event.target as HTMLInputElement)?.value)"
16
16
  :value="modelValue"
17
17
  aria-describedby="helper-text-explanation"
18
- class="inline-flex bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
19
- blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 dark:text-white translate-y-0"
18
+ class="afcl-input inline-flex bg-lightInputBackground border border-lightInputBorder text-lightInputText text-sm rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
19
+ blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-darkInputBackground dark:border-darkInputBorder placeholder-lightInputPlaceholderText dark:placeholder-darkInputPlaceholderText dark:text-darkInputText translate-y-0"
20
20
  :class="{'rounded-l-md': !$slots.prefix && !prefix, 'rounded-r-md': !$slots.suffix && !suffix, 'w-full': fullWidth}"
21
21
  :disabled="readonly"
22
22
  >
@@ -26,7 +26,7 @@
26
26
  </div>
27
27
  <span
28
28
  v-if="$slots.suffix || suffix"
29
- class="inline-flex items-center px-3 text-sm text-gray-900 bg-gray-200 border border-s-0 border-gray-300 rounded-e-md dark:bg-gray-600 dark:text-gray-400 dark:border-gray-600 ">
29
+ class="inline-flex items-center px-3 text-sm text-lightInputText bg-lightInputBackground border border-s-0 border-lightInputBorder rounded-e-md dark:bg-darkInputBackground dark:text-darkInputText dark:border-darkInputBorder ">
30
30
  <slot name="suffix">{{ suffix }}</slot>
31
31
  </span>
32
32
 
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <JsonViewer
3
+ class="afcl-json-viewer min-w-[6rem]"
4
+ :value="value"
5
+ :expandDepth="expandDepth"
6
+ copyable
7
+ sort
8
+ :theme="currentTheme"
9
+ />
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { computed } from 'vue'
14
+ import { JsonViewer } from 'vue3-json-viewer'
15
+ import { useCoreStore } from '@/stores/core'
16
+
17
+ defineProps<{
18
+ value: any
19
+ expandDepth?: number
20
+ }>()
21
+
22
+ const coreStore = useCoreStore()
23
+
24
+ const currentTheme = computed(() => (coreStore.theme === 'dark' ? 'dark' : 'light'))
25
+ </script>
@@ -3,8 +3,8 @@
3
3
  v-bind="$attrs"
4
4
  :to="props.to"
5
5
  type="submit"
6
- class="afcl-link-button flex items-center justify-center gap-1 text-lightPrimaryContrast bg-lightPrimary dark:bg-darkPrimary hover:brightness-110
7
- focus:ring-4 focus:outline-none focus:ring-lightPrimary focus:ring-opacity-50 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-darkPrimary dark:focus:ring-opacity-50"
6
+ class="afcl-link-button flex items-center justify-center gap-1 text-lightButtonsText bg-lightButtonsBackground border border-lightButtonsBorder dark:bg-darkButtonsBackground hover:bg-lightButtonsHover hover:border-lightButtonsBorderHover
7
+ focus:ring-4 focus:outline-none focus:ring-lightButtonFocusRing focus:ring-opacity-50 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-darkButtonFocusRing dark:text-darkButtonsText dark:border-darkButtonsBorder dark:hover:bg-darkButtonsHover dark:hover:border-darkButtonsBorderHover"
8
8
  :class="{
9
9
  'cursor-default': props.disabled,
10
10
  'opacity-50': props.disabled,
@@ -18,7 +18,7 @@
18
18
  <script setup lang="ts">
19
19
 
20
20
  const props = defineProps<{
21
- disabled: boolean,
21
+ disabled?: boolean,
22
22
  to: string,
23
23
  }>();
24
24
 
@@ -63,8 +63,8 @@ const optionsBase = {
63
63
  show: false,
64
64
  fontFamily: "Inter, sans-serif",
65
65
  label: "",
66
- formatter: function (w) {
67
- const sum = w.globals.seriesTotals.reduce((a, b) => {
66
+ formatter: function (w: any) {
67
+ const sum = w.globals.seriesTotals.reduce((a: any, b: any) => {
68
68
  return a + b
69
69
  }, 0)
70
70
  return sum
@@ -74,7 +74,7 @@ const optionsBase = {
74
74
  show: true,
75
75
  fontFamily: "Inter, sans-serif",
76
76
  offsetY: -20,
77
- formatter: function (value) {
77
+ formatter: function (value: any) {
78
78
  return value + "k"
79
79
  },
80
80
  },
@@ -100,14 +100,14 @@ const optionsBase = {
100
100
  },
101
101
  yaxis: {
102
102
  labels: {
103
- formatter: function (value) {
103
+ formatter: function (value: any) {
104
104
  return value;
105
105
  },
106
106
  },
107
107
  },
108
108
  xaxis: {
109
109
  labels: {
110
- formatter: function (value) {
110
+ formatter: function (value: any) {
111
111
  return value;
112
112
  },
113
113
  },
@@ -1,14 +1,14 @@
1
1
  <template>
2
- <div class="relative mt-4 lg:mt-10 w-full max-w-[700px] bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
3
- <span class="absolute -top-6 left-0 text-sm text-gray-500">{{ leftLabel }}</span>
4
- <span class="absolute -top-6 right-0 text-sm text-gray-500">{{ rightLabel }}</span>
2
+ <div class="relative mt-4 lg:mt-10 w-full max-w-[700px] bg-lightProgressBarUnfilledColor rounded-full h-2.5 dark:bg-darkProgressBarUnfilledColor">
3
+ <span class="absolute -top-6 left-0 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ leftLabel }}</span>
4
+ <span class="absolute -top-6 right-0 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ rightLabel }}</span>
5
5
  <div
6
- class="bg-lightPrimary dark:bg-darkPrimary h-2.5 rounded-full transition-all duration-300 ease-in-out"
6
+ class="bg-lightProgressBarFilledColor dark:bg-darkProgressBarFilledColor h-2.5 rounded-full transition-all duration-300 ease-in-out"
7
7
  :style="{ width: `${percentage}%` }"
8
8
  ></div>
9
- <span v-if="showValues" class="absolute top-4 left-0 text-sm text-gray-500">{{ formatValue(minValue) }}</span>
10
- <span v-if="showProgress" class="absolute top-4 right-1/2 translate-x-1/2 text-sm text-gray-500">{{ progressText }}</span>
11
- <span v-if="showValues" class="absolute top-4 right-0 text-sm text-gray-500">{{ formatValue(maxValue) }}</span>
9
+ <span v-if="showValues" class="absolute top-4 left-0 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ formatValue(minValue) }}</span>
10
+ <span v-if="showProgress" class="absolute top-4 right-1/2 translate-x-1/2 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ progressText }}</span>
11
+ <span v-if="showValues" class="absolute top-4 right-0 text-sm text-lightProgressBarText dark:text-darkProgressBarText">{{ formatValue(maxValue) }}</span>
12
12
  </div>
13
13
  </template>
14
14
 
@@ -1,55 +1,58 @@
1
1
  <template>
2
- <div class="afcl-select relative inline-block" ref="internalSelect"
2
+ <div class="afcl-select afcl-select-wrapper relative inline-block" ref="internalSelect"
3
3
  :class="{'opacity-50': readonly}"
4
4
  >
5
5
  <div class="relative">
6
6
  <input
7
7
  ref="inputEl"
8
8
  type="text"
9
- :readonly="readonly"
9
+ :readonly="readonly || searchDisabled"
10
10
  v-model="search"
11
11
  @click="inputClick"
12
12
  @input="inputInput"
13
- class="block w-full pl-3 pr-10 py-2.5 border border-gray-300 rounded-md leading-5 bg-gray-50 placeholder-gray-500 sm:text-sm transition duration-150 ease-in-out dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary"
13
+ class="block w-full pl-3 pr-10 py-2.5 border border-lightDropownButtonsBorder rounded-md leading-5 bg-lightDropdownButtonsBackground
14
+ placeholder-lightDropdownButtonsPlaceholderText text-lightDropdownButtonsText sm:text-sm transition duration-150 ease-in-out dark:bg-darkDropdownButtonsBackground dark:border-darkDropdownButtonsBorder dark:placeholder-darkDropdownButtonsPlaceholderText
15
+ dark:text-darkDropdownButtonsText focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary"
14
16
  autocomplete="off" data-custom="no-autofill"
15
17
  :placeholder="
16
18
  selectedItems.length && !multiple ? '' : (showDropdown ? $t('Search') : placeholder || $t('Select...'))
17
19
  "
18
20
  />
19
21
 
20
- <div v-if="!multiple && selectedItems.length" class="absolute pointer-events-none inset-y-0 left-2 flex items-center pr-2 px-1">
22
+ <div v-if="!multiple && selectedItems.length" class="text-lightDropdownButtonsText dark:text-darkDropdownButtonsText absolute pointer-events-none inset-y-0 left-2 flex items-center pr-2 px-1">
21
23
  <slot
22
24
  name="selected-item"
23
25
  :option="selectedItems[0]"
24
26
  ></slot>
25
- <span v-if="!$slots['selected-item']" class="text-lightPrimary dark:text-white font-medium ">
27
+ <span v-if="!$slots['selected-item']" class="text-lightDropdownButtonsText dark:text-darkDropdownButtonsText font-medium ">
26
28
  {{ selectedItems[0]?.label }}
27
29
  </span>
28
30
  </div>
29
31
 
30
32
  <div class="absolute inset-y-0 right-2 flex items-center pointer-events-none">
31
33
  <!-- triangle icon -->
32
- <IconCaretDownSolid class="h-5 w-5 text-lightPrimary dark:text-gray-400 opacity-50 transition duration-150 ease-in"
34
+ <IconCaretDownSolid class="h-5 w-5 text-lightPrimary dark:text-darkPrimary opacity-50 transition duration-150 ease-in"
33
35
  :class="{ 'transform rotate-180': showDropdown }"
34
36
  />
35
37
  </div>
36
38
  </div>
37
- <teleport to="body" v-if="teleportToBody && showDropdown">
38
- <div ref="dropdownEl" :style="getDropdownPosition" :class="{'shadow-none': isTop}"
39
- class="fixed z-[5] w-full bg-white shadow-lg dark:shadow-black dark:bg-gray-700
40
- dark:border-gray-600 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm max-h-48">
39
+ <teleport to="body" v-if="(teleportToBody || teleportToTop) && showDropdown">
40
+ <div ref="dropdownEl" :style="getDropdownPosition" :class="{'shadow-none': isTop, 'z-[5]': teleportToBody, 'z-[1000]': teleportToTop}"
41
+ class="fixed w-full bg-lightDropdownOptionsBackground shadow-lg dark:shadow-black dark:bg-darkDropdownOptionsBackground
42
+ dark:border-gray-600 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm max-h-48"
43
+ @scroll="handleDropdownScroll">
41
44
  <div
42
45
  v-for="item in filteredItems"
43
46
  :key="item.value"
44
- class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400"
45
- :class="{ 'bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity': selectedItems.includes(item) }"
47
+ class="px-4 py-2 cursor-pointer hover:bg-lightDropdownOptionsHoverBackground dark:hover:bg-darkDropdownOptionsHoverBackground text-lightDropdownOptionsText dark:text-darkDropdownOptionsText"
48
+ :class="{ 'bg-lightDropdownPicked dark:bg-darkDropdownPicked': selectedItems.includes(item) }"
46
49
  @click="toogleItem(item)"
47
50
  >
48
51
  <slot name="item" :option="item"></slot>
49
52
  <label v-if="!$slots.item" :for="item.value">{{ item.label }}</label>
50
53
  </div>
51
- <div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-gray-400 dark:text-gray-300">
52
- {{ options.length ? $t('No results found') : $t('No items here') }}
54
+ <div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-lightDropdownOptionsText dark:text-darkDropdownOptionsText">
55
+ {{ options?.length ? $t('No results found') : $t('No items here') }}
53
56
  </div>
54
57
 
55
58
  <div v-if="$slots['extra-item']" class="px-4 py-2 dark:text-gray-400">
@@ -58,24 +61,24 @@
58
61
  </div>
59
62
  </teleport>
60
63
 
61
- <div v-if="!teleportToBody && showDropdown" ref="dropdownEl" :style="dropdownStyle" :class="{'shadow-none': isTop}"
62
- class="absolute z-10 mt-1 w-full bg-white shadow-lg dark:shadow-black dark:bg-gray-700
63
- dark:border-gray-600 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm max-h-48">
64
+ <div v-if="!teleportToBody && !teleportToTop && showDropdown" ref="dropdownEl" :style="dropdownStyle" :class="{'shadow-none': isTop}"
65
+ class="absolute z-10 mt-1 w-full bg-lightDropdownOptionsBackground shadow-lg text-lightDropdownButtonsText dark:shadow-black dark:bg-darkDropdownOptionsBackground
66
+ dark:border-gray-600 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm max-h-48"
67
+ @scroll="handleDropdownScroll">
64
68
  <div
65
69
  v-for="item in filteredItems"
66
70
  :key="item.value"
67
- class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400"
68
- :class="{ 'bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity': selectedItems.includes(item) }"
71
+ class="px-4 py-2 cursor-pointer text-lightDropdownOptionsText hover:bg-lightDropdownOptionsHoverBackground dark:hover:bg-darkDropdownOptionsHoverBackground dark:text-darkDropdownOptionsText"
72
+ :class="{ 'bg-lightDropdownPicked dark:bg-darkDropdownPicked': selectedItems.includes(item) }"
69
73
  @click="toogleItem(item)"
70
74
  >
71
75
  <slot name="item" :option="item"></slot>
72
76
  <label v-if="!$slots.item" :for="item.value">{{ item.label }}</label>
73
77
  </div>
74
- <div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-gray-400 dark:text-gray-300">
75
- {{ options.length ? $t('No results found') : $t('No items here') }}
78
+ <div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-lightDropdownOptionsText dark:text-darkDropdownOptionsText">
79
+ {{ options?.length ? $t('No results found') : $t('No items here') }}
76
80
  </div>
77
-
78
- <div v-if="$slots['extra-item']" class="px-4 py-2 dark:text-gray-400">
81
+ <div v-if="$slots['extra-item']" class="px-4 py-2 dark:text-darkDropdownOptionsText">
79
82
  <slot name="extra-item"></slot>
80
83
  </div>
81
84
 
@@ -87,12 +90,12 @@
87
90
  <template v-for="item in selectedItems" :key="`afcl-select-${item.value}`">
88
91
  <slot name="selected-item" :item="item"></slot>
89
92
  <div v-if="!$slots['selected-item']"
90
- class="bg-lightPrimaryOpacity text-lightPrimary text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-darkPrimaryOpacity dark:text-darkPrimary">
93
+ class="bg-lightDropdownMultipleSelectBackground text-lightDropdownMultipleSelectText text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-darkDropdownMultipleSelectBackground dark:text-darkDropdownMultipleSelectText">
91
94
  <span>{{ item.label }}</span>
92
95
  <button
93
96
  type="button"
94
97
  @click="toogleItem(item)"
95
- class="z-index-100 flex-shrink-0 ml-1 h-4 w-4 -mr-1 rounded-full inline-flex items-center justify-center text-gray-400 hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100"
98
+ class="z-index-100 flex-shrink-0 ml-1 h-4 w-4 -mr-1 rounded-full inline-flex items-center justify-center text-lightDropdownMultipleSelectIcon hover:text-lightDropdownMultipleSelectIconHover dark:text-darkDropdownMultipleSelectIcon dark:hover:text-darkDropdownMultipleSelectIconHover focus:outline-none focus:text-lightDropdownMultipleSelectIconFocus focus:bg-lightDropdownMultipleSelectIconFocusBackground dark:focus:text-darkDropdownMultipleSelectIconFocus dark:focus:bg-darkDropdownMultipleSelectIconFocusBackground"
96
99
  >
97
100
  <span class="sr-only">{{ $t('Remove item') }}</span>
98
101
  <svg class="h-2 w-2" stroke="currentColor" fill="none" viewBox="0 0 8 8">
@@ -111,14 +114,15 @@
111
114
  </template>
112
115
 
113
116
  <script setup lang="ts">
114
- import { ref, computed, onMounted, onUnmounted, watch, type Ref } from 'vue';
117
+ import { ref, computed, onMounted, onUnmounted, watch, nextTick,type PropType, type Ref } from 'vue';
115
118
  import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
116
119
  import { useElementSize } from '@vueuse/core'
117
120
 
118
121
  const props = defineProps({
119
122
  options: Array,
120
123
  modelValue: {
121
- default: undefined,
124
+ type: Array as PropType<(string | number)[]>,
125
+ default: () => [],
122
126
  },
123
127
  multiple: {
124
128
  type: Boolean,
@@ -132,13 +136,21 @@ const props = defineProps({
132
136
  type: Boolean,
133
137
  default: false,
134
138
  },
139
+ searchDisabled: {
140
+ type: Boolean,
141
+ default: false,
142
+ },
135
143
  teleportToBody: {
136
144
  type: Boolean,
137
145
  default: false,
138
146
  },
147
+ teleportToTop: {
148
+ type: Boolean,
149
+ default: false,
150
+ },
139
151
  });
140
152
 
141
- const emit = defineEmits(['update:modelValue']);
153
+ const emit = defineEmits(['update:modelValue', 'scroll-near-end', 'search']);
142
154
 
143
155
  const search = ref('');
144
156
  const showDropdown = ref(false);
@@ -159,24 +171,27 @@ function inputInput() {
159
171
  selectedItems.value = [];
160
172
  emit('update:modelValue', null);
161
173
  }
174
+ if (!props.searchDisabled) {
175
+ emit('search', search.value);
176
+ }
162
177
  }
163
178
 
164
179
  function updateFromProps() {
165
180
  if (props.modelValue !== undefined) {
166
181
  if (!props.multiple) {
167
- const el = props.options.find(item => item.value === props.modelValue);
182
+ const el = props.options?.find((item: any) => item.value === props.modelValue);
168
183
  if (el) {
169
184
  selectedItems.value = [el];
170
185
  } else {
171
186
  selectedItems.value = [];
172
187
  }
173
188
  } else {
174
- selectedItems.value = props.options.filter(item => props.modelValue.includes(item.value));
189
+ selectedItems.value = props.options?.filter((item: any) => props.modelValue?.includes(item.value)) || [];
175
190
  }
176
191
  }
177
192
  }
178
193
 
179
- function inputClick() {
194
+ async function inputClick() {
180
195
  if (props.readonly) return;
181
196
  // Toggle local dropdown
182
197
  showDropdown.value = !showDropdown.value;
@@ -184,6 +199,11 @@ function inputClick() {
184
199
  if (!showDropdown.value && !search.value) {
185
200
  search.value = '';
186
201
  }
202
+
203
+ if(props.teleportToBody){
204
+ await nextTick();
205
+ handleScroll();
206
+ }
187
207
  }
188
208
 
189
209
  watch(
@@ -221,6 +241,15 @@ const handleScroll = () => {
221
241
  }
222
242
  };
223
243
 
244
+ const handleDropdownScroll = (event: Event) => {
245
+ const target = event.target as HTMLElement;
246
+ const threshold = 10; // pixels from bottom
247
+
248
+ if (target.scrollTop + target.clientHeight >= target.scrollHeight - threshold) {
249
+ emit('scroll-near-end');
250
+ }
251
+ };
252
+
224
253
  onMounted(() => {
225
254
  updateFromProps();
226
255
 
@@ -240,8 +269,13 @@ onMounted(() => {
240
269
  }
241
270
  });
242
271
 
243
- const filteredItems = computed(() => {
244
- return props.options.filter(item =>
272
+ const filteredItems: Ref<any[]> = computed(() => {
273
+
274
+ if (props.searchDisabled) {
275
+ return props.options || [];
276
+ }
277
+
278
+ return (props.options || []).filter((item: any) =>
245
279
  item.label.toLowerCase().includes(search.value.toLowerCase())
246
280
  );
247
281
  });
@@ -262,7 +296,7 @@ const removeClickListener = () => {
262
296
  document.removeEventListener('click', handleClickOutside);
263
297
  };
264
298
 
265
- const toogleItem = (item) => {
299
+ const toogleItem = (item: any) => {
266
300
  if (selectedItems.value.includes(item)) {
267
301
  selectedItems.value = selectedItems.value.filter(i => i.value !== item.value);
268
302
  } else {
@@ -1,20 +1,20 @@
1
1
  <template>
2
- <div v-if="type === 'image'" role="status" class="flex animate-pulse items-center justify-center bg-gray-300 rounded dark:bg-gray-700">
3
- <svg class="w-10 h-10 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
2
+ <div v-if="type === 'image'" role="status" class="flex animate-pulse items-center justify-center bg-lightSkeletonBackgroundColor rounded dark:bg-darkSkeletonBackgroundColor">
3
+ <svg class="w-10 h-10 text-lightSkeletonIconColor dark:text-darkSkeletonIconColor" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
4
4
  <path d="M18 0H2a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2Zm-5.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm4.376 10.481A1 1 0 0 1 16 15H4a1 1 0 0 1-.895-1.447l3.5-7A1 1 0 0 1 7.468 6a.965.965 0 0 1 .9.5l2.775 4.757 1.546-1.887a1 1 0 0 1 1.618.1l2.541 4a1 1 0 0 1 .028 1.011Z"/>
5
5
  </svg>
6
6
  </div>
7
- <div v-else-if="type === 'video'" role="status" class="flex items-center justify-center max-w-sm bg-gray-300 rounded-lg animate-pulse dark:bg-gray-700">
8
- <svg class="w-10 h-10 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 20">
7
+ <div v-else-if="type === 'video'" role="status" class="flex items-center justify-center max-w-sm bg-lightSkeletonBackgroundColor rounded-lg animate-pulse dark:bg-darkSkeletonBackgroundColor">
8
+ <svg class="w-10 h-10 text-lightSkeletonIconColor dark:text-darkSkeletonIconColor" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 20">
9
9
  <path d="M5 5V.13a2.96 2.96 0 0 0-1.293.749L.879 3.707A2.98 2.98 0 0 0 .13 5H5Z"/>
10
10
  <path d="M14.066 0H7v5a2 2 0 0 1-2 2H0v11a1.97 1.97 0 0 0 1.934 2h12.132A1.97 1.97 0 0 0 16 18V2a1.97 1.97 0 0 0-1.934-2ZM9 13a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2Zm4 .382a1 1 0 0 1-1.447.894L10 13v-2l1.553-1.276a1 1 0 0 1 1.447.894v2.764Z"/>
11
11
  </svg>
12
12
  <span class="sr-only">Loading...</span>
13
13
  </div>
14
- <svg v-else-if="type === 'avatar'" class="me-3 animate-pulse text-gray-200 dark:text-gray-700" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
14
+ <svg v-else-if="type === 'avatar'" class="me-3 animate-pulse text-lightSkeletonIconColor dark:text-darkSkeletonBackgroundColor" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
15
15
  <path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"/>
16
16
  </svg>
17
- <div v-else role="status" class="flex items-center justify-center animate-pulse bg-gray-200 rounded-full dark:bg-gray-700">
17
+ <div v-else role="status" class="flex items-center justify-center animate-pulse bg-lightSkeletonIconColor rounded-full dark:bg-darkSkeletonBackgroundColor">
18
18
  <span class="sr-only">Loading...</span>
19
19
  </div>
20
20
  </template>