@winchsa/ui 0.1.35 → 0.1.37

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.
@@ -35,12 +35,14 @@ type __VLS_Props = {
35
35
  isLoading?: boolean;
36
36
  disableIfEmpty?: boolean;
37
37
  clearable?: boolean;
38
+ showNoOptions?: boolean;
39
+ showNoResults?: boolean;
38
40
  };
39
- declare var __VLS_71: string | number, __VLS_72: any, __VLS_136: string | number, __VLS_137: any;
41
+ declare var __VLS_69: string | number, __VLS_70: any, __VLS_134: string | number, __VLS_135: any;
40
42
  type __VLS_Slots = {} & {
41
- [K in NonNullable<typeof __VLS_71>]?: (props: typeof __VLS_72) => any;
43
+ [K in NonNullable<typeof __VLS_69>]?: (props: typeof __VLS_70) => any;
42
44
  } & {
43
- [K in NonNullable<typeof __VLS_136>]?: (props: typeof __VLS_137) => any;
45
+ [K in NonNullable<typeof __VLS_134>]?: (props: typeof __VLS_135) => any;
44
46
  };
45
47
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
46
48
  records: Ref<{
@@ -1,10 +1,10 @@
1
1
  <script setup>
2
2
  import MultiSelect from "vue-multiselect";
3
- import { ref, computed, watch, useAttrs, nextTick } from "vue";
3
+ import { ref, computed, watch, useAttrs } from "vue";
4
4
  import { useI18n } from "vue-i18n";
5
5
  import { VLabel, VValidation, VListItem, VMessages, VIcon } from "vuetify/components";
6
6
  import { client } from "../../utils/client";
7
- import { generateUniqueId } from "../../utils/utils";
7
+ import { generateUniqueId, searchArabicText } from "../../utils/utils";
8
8
  import { buildQueryString } from "../../utils/queryParams";
9
9
  import LoadingBar from "../LoadingBar.vue";
10
10
  const { t } = useI18n();
@@ -43,8 +43,11 @@ const props = defineProps({
43
43
  fetchEnabled: { type: Boolean, required: false, default: true },
44
44
  isLoading: { type: Boolean, required: false, default: false },
45
45
  disableIfEmpty: { type: Boolean, required: false, default: false },
46
- clearable: { type: Boolean, required: false, default: true }
46
+ clearable: { type: Boolean, required: false, default: true },
47
+ showNoOptions: { type: Boolean, required: false, default: true },
48
+ showNoResults: { type: Boolean, required: false, default: true }
47
49
  });
50
+ const emits = defineEmits(["update:model-value"]);
48
51
  const defaultOptions = ref(props.options ?? []);
49
52
  const displayedOptions = ref(props.options ?? []);
50
53
  const loadedValuesHaveBeenSet = ref(false);
@@ -52,10 +55,8 @@ const loading = ref(false);
52
55
  const showTeleport = ref(false);
53
56
  const search = ref("");
54
57
  const inputRef = ref({});
55
- const teleportRef = ref(null);
56
58
  const errorState = ref(false);
57
59
  const isOpen = ref(false);
58
- const emits = defineEmits(["update:model-value"]);
59
60
  const value = computed({
60
61
  get() {
61
62
  return props.modelValue;
@@ -64,9 +65,7 @@ const value = computed({
64
65
  emits("update:model-value", value2);
65
66
  }
66
67
  });
67
- const isLoading = computed(() => {
68
- return props.isLoading || loading.value;
69
- });
68
+ const isLoading = computed(() => props.isLoading || loading.value);
70
69
  const params = computed(() => buildQueryString(props.params));
71
70
  const elementId = computed(() => {
72
71
  const attrs = useAttrs();
@@ -74,10 +73,59 @@ const elementId = computed(() => {
74
73
  return _elementIdToken ? `autocomplete-input-${_elementIdToken}-${guid}` : `autocomplete-input-${guid}`;
75
74
  });
76
75
  const internalPlaceholder = computed(() => {
77
- return props.placeholder ? props.placeholder : t("inputs.placeholder_select", {
78
- name: props.label ? t(props.label) : ""
79
- });
76
+ return props.placeholder ?? t("inputs.placeholder_select", { name: props.label ? t(props.label) : "" });
77
+ });
78
+ const hasValue = computed(() => Array.isArray(value.value) ? value.value.length > 0 : !!value.value);
79
+ const hasError = computed(() => (validationIsValid) => {
80
+ return props.error || validationIsValid === false || errorState.value;
80
81
  });
82
+ const filteredOptions = computed(() => {
83
+ if (!search.value) {
84
+ return displayedOptions.value;
85
+ }
86
+ return displayedOptions.value?.map((entry) => {
87
+ if (props.grouped) {
88
+ const matchingChildren = entry?.children?.filter((child) => {
89
+ const childValue = getOptionValue(child);
90
+ return searchArabicText(childValue, search.value);
91
+ });
92
+ return matchingChildren?.length ? { ...entry, children: matchingChildren } : null;
93
+ }
94
+ const entryValue = getOptionValue(entry);
95
+ return searchArabicText(entryValue, search.value) ? entry : null;
96
+ }).filter(Boolean);
97
+ });
98
+ const disableIfNoOptions = computed(() => filteredOptions.value.length === 0 && props.disableIfEmpty);
99
+ const sharedMultiSelectProps = computed(() => ({
100
+ options: filteredOptions.value,
101
+ multiple: props.multiple,
102
+ closeOnSelect: !props.multiple,
103
+ searchable: props.searchable,
104
+ internalSearch: false,
105
+ preselectFirst: props.preselectFirst,
106
+ disabled: props.disabled || disableIfNoOptions.value,
107
+ required: props.required,
108
+ groupSelect: props.grouped,
109
+ groupValues: props.groupValues,
110
+ groupLabel: props.groupLabel,
111
+ label: props.itemLabel,
112
+ trackBy: props.trackBy,
113
+ placeholder: hasValue.value ? "" : internalPlaceholder.value || t("inputs.placeholder_select"),
114
+ showLabels: false,
115
+ clearOnSelect: true,
116
+ showNoOptions: isLoading.value ? false : props.showNoOptions,
117
+ showNoResults: isLoading.value ? false : props.showNoResults,
118
+ deselectLabel: props.deselectLabel,
119
+ hideSelected: props.hideSelected,
120
+ selectLabel: props.selectLabel,
121
+ selectedLabel: props.selectedLabel
122
+ }));
123
+ const getOptionValue = (option) => {
124
+ if (typeof option === "object" && option !== null && "name" in option) {
125
+ return option.name?.toString() ?? "";
126
+ }
127
+ return option?.toString() || "";
128
+ };
81
129
  const customPosition = () => {
82
130
  const autocompleteInputElement = document.querySelector(`#${elementId.value}`);
83
131
  const rect = autocompleteInputElement?.getBoundingClientRect();
@@ -88,26 +136,22 @@ const customPosition = () => {
88
136
  height: rect?.height
89
137
  };
90
138
  };
91
- const normalizeArabic = (text) => {
92
- if (typeof text === "number") {
93
- return text.toString();
94
- }
95
- return text.trim().replace(/أ|ا|إ|آ/g, "\u0627").replace(/ي|ى|ئ/g, "\u064A").replace(/ة/g, "\u0647");
139
+ const customSearch = (query) => {
140
+ search.value = query?.toString().trim() || "";
96
141
  };
97
- const searchArabic = (value2, query) => {
98
- if (!value2 || !query) {
99
- return false;
142
+ const handleOpen = () => {
143
+ if (props.teleported) {
144
+ showTeleport.value = true;
100
145
  }
101
- const normalizedValue = normalizeArabic(value2.toString()).toLowerCase();
102
- const normalizedQuery = normalizeArabic(query).toLowerCase();
103
- return normalizedValue.includes(normalizedQuery);
146
+ isOpen.value = true;
104
147
  };
105
- const customSearch = (query) => {
106
- search.value = query?.toString().trim() || "";
148
+ const handleClose = () => {
149
+ showTeleport.value = false;
150
+ isOpen.value = false;
107
151
  };
108
152
  const resetTeleportState = () => {
109
153
  if (!props.multiple && props.teleported) {
110
- showTeleport.value = false;
154
+ handleClose();
111
155
  inputRef.value[`select-input-${elementId.value}`]?.deactivate();
112
156
  }
113
157
  };
@@ -132,98 +176,62 @@ const getRecords = async (searchQuery) => {
132
176
  const fetchAndUpdateRecords = async (searchQuery) => {
133
177
  displayedOptions.value = await getRecords(searchQuery);
134
178
  };
135
- watch(
136
- [() => props.trackByValue, defaultOptions],
137
- async ([trackByValue, _defaultOptions]) => {
138
- if (!trackByValue || _defaultOptions.length === 0 || loadedValuesHaveBeenSet.value) {
139
- return;
140
- }
141
- loadedValuesHaveBeenSet.value = true;
142
- const loadedValues = Array.isArray(trackByValue) ? trackByValue : [trackByValue];
143
- let items = _defaultOptions.flatMap((option) => props.grouped ? option.children : [option]);
144
- const missingIds = loadedValues.filter((value2) => !items.some((item) => item?.[props.trackBy] == value2));
145
- if (missingIds.length > 0 && props.url) {
146
- const fetchedItems = await Promise.all(missingIds.map((id) => getRecords(id)));
147
- if (fetchedItems.length > 0) {
148
- items = items.concat(fetchedItems.flat());
149
- }
150
- }
151
- const optionsToSelect = loadedValues.map((value2) => items.find((item) => item?.[props.trackBy] == value2)).filter(Boolean);
152
- if (optionsToSelect.length > 0) {
153
- emits("update:model-value", optionsToSelect);
179
+ const updateOptions = async (newOptions) => {
180
+ defaultOptions.value = newOptions;
181
+ displayedOptions.value = newOptions;
182
+ };
183
+ const handleSelect = () => {
184
+ errorState.value = false;
185
+ resetTeleportState();
186
+ };
187
+ const handleSearchChange = (query) => {
188
+ if (props.searchInternally) {
189
+ customSearch(query);
190
+ } else {
191
+ fetchAndUpdateRecords(query?.toString());
192
+ }
193
+ };
194
+ watch([() => props.trackByValue, defaultOptions], async ([trackByValue, _defaultOptions]) => {
195
+ if (!trackByValue || _defaultOptions.length === 0 || loadedValuesHaveBeenSet.value) {
196
+ return;
197
+ }
198
+ loadedValuesHaveBeenSet.value = true;
199
+ const loadedValues = Array.isArray(trackByValue) ? trackByValue : [trackByValue];
200
+ let items = _defaultOptions.flatMap((option) => props.grouped ? option.children : [option]);
201
+ const missingIds = loadedValues.filter(
202
+ (value2) => !items.some((item) => item?.[props.trackBy] == value2)
203
+ );
204
+ if (missingIds.length > 0 && props.url) {
205
+ const fetchedItems = await Promise.all(missingIds.map((id) => getRecords(id)));
206
+ if (fetchedItems.length > 0) {
207
+ items = items.concat(fetchedItems.flat());
154
208
  }
155
- },
156
- {
157
- immediate: true
158
209
  }
159
- );
160
- watch(() => props.errorMessages, () => {
161
- if (props.errorMessages && props.errorMessages.length > 0) {
162
- errorState.value = true;
210
+ const optionsToSelect = loadedValues.map((value2) => items.find((item) => item?.[props.trackBy] == value2)).filter(Boolean);
211
+ if (optionsToSelect.length > 0) {
212
+ emits("update:model-value", optionsToSelect);
163
213
  }
164
- }, {
165
- deep: true,
166
- immediate: true
167
- });
214
+ }, { immediate: true });
215
+ watch(() => props.errorMessages, (newErrorMessages) => {
216
+ errorState.value = !!(newErrorMessages && newErrorMessages.length > 0);
217
+ }, { deep: true, immediate: true });
168
218
  watch(() => params.value, (value2) => {
169
219
  if (value2) {
170
220
  fetchAndUpdateRecords();
171
221
  }
172
222
  });
173
- const filteredOptions = computed(() => {
174
- if (!search.value) {
175
- return displayedOptions.value;
176
- }
177
- return displayedOptions.value?.map((entry) => {
178
- if (props.grouped) {
179
- const matchingChildren = entry?.children?.filter((child) => {
180
- const childValue = typeof child === "object" && child !== null && "name" in child ? child.name : child;
181
- return searchArabic(childValue?.toString() ?? "", search.value);
182
- });
183
- if (matchingChildren && matchingChildren.length > 0) {
184
- return {
185
- ...entry,
186
- children: matchingChildren
187
- };
188
- }
189
- return null;
190
- }
191
- const entryValue = typeof entry === "object" && entry !== null && "name" in entry ? entry.name : entry;
192
- return searchArabic(entryValue?.toString() || "", search.value) ? entry : null;
193
- }).filter(Boolean);
194
- });
195
- const disableIfNoOptions = computed(() => filteredOptions.value.length === 0 && props.disableIfEmpty);
196
- watch(() => props.url, async () => {
197
- if (props.fetchEnabled && props.url) {
198
- const options = await getRecords();
199
- defaultOptions.value = options;
200
- displayedOptions.value = options;
201
- }
202
- }, {
203
- immediate: true,
204
- deep: true
205
- });
206
- watch(() => props.options, () => {
223
+ watch([() => props.url, () => props.options], async () => {
207
224
  if (props.options && props.options.length > 0) {
208
- defaultOptions.value = props.options;
209
- displayedOptions.value = props.options;
210
- }
211
- }, {
212
- deep: true,
213
- immediate: true
214
- });
215
- watch(showTeleport, (newValue) => {
216
- if (newValue && props.teleported) {
217
- nextTick(() => {
218
- teleportRef.value?.activate();
219
- });
225
+ updateOptions(props.options);
226
+ } else if (props.fetchEnabled && props.url) {
227
+ const options = await getRecords();
228
+ updateOptions(options);
220
229
  }
221
- });
230
+ }, { deep: true, immediate: true });
222
231
  const refetch = async () => {
223
232
  if (props.url) {
224
233
  const options = await getRecords();
225
- defaultOptions.value = options;
226
- displayedOptions.value = options;
234
+ updateOptions(options);
227
235
  return options;
228
236
  }
229
237
  return [];
@@ -252,22 +260,11 @@ defineExpose({
252
260
  validate-on="input lazy"
253
261
  >
254
262
  <MultiSelect
255
- ref="teleportRef"
256
263
  v-model="value"
257
- :options="filteredOptions"
258
- :multiple="multiple"
259
- :close-on-select="multiple ? false : true"
260
- :searchable="searchable"
261
- :internal-search="false"
262
- :preselect-first="preselectFirst"
263
- :disabled="disabled || disableIfNoOptions"
264
- :required="required"
265
- :group-select="grouped"
266
- :group-values="groupValues"
267
- :group-label="groupLabel"
264
+ v-bind="sharedMultiSelectProps"
268
265
  class="app-custom-input position-absolute app-z-7 app-custom-input-teleport"
269
266
  :class="{
270
- 'app-custom-input-error': error || isValid.value === false || errorState
267
+ 'app-custom-input-error': hasError(isValid.value)
271
268
  }"
272
269
  :style="{
273
270
  left: `${customPosition().left}px`,
@@ -275,25 +272,19 @@ defineExpose({
275
272
  width: `${customPosition().width}px`,
276
273
  height: `${customPosition().height}px`
277
274
  }"
278
- :label="itemLabel"
279
- :track-by="trackBy"
280
- :placeholder="value && value.length > 0 ? '' : internalPlaceholder || t('inputs.placeholder_select')"
281
- :show-labels="false"
282
- :clear-on-select="true"
283
- :show-no-options="isLoading ? false : true"
284
- :show-no-results="isLoading ? false : true"
285
- :deselect-label="deselectLabel"
286
- :hide-selected="hideSelected"
287
- :select-label="selectLabel"
288
- :selected-label="selectedLabel"
289
275
  :value="value"
290
- @search-change="searchInternally ? customSearch($event) : fetchAndUpdateRecords($event)"
291
- @select="errorState = false, resetTeleportState()"
292
- @close="showTeleport = false, isOpen = false"
276
+ @search-change="handleSearchChange"
277
+ @select="handleSelect"
278
+ @close="handleClose"
293
279
  >
294
280
  <template #caret>
295
281
  <span class="multiselect-arrow">
296
- <VIcon class="icon" size="19">tabler-chevron-down</VIcon>
282
+ <VIcon
283
+ icon="tabler-chevron-down"
284
+ class="icon"
285
+ size="19"
286
+ :color="hasError(isValid.value) ? 'error' : ''"
287
+ />
297
288
  </span>
298
289
  </template>
299
290
 
@@ -328,13 +319,16 @@ defineExpose({
328
319
 
329
320
  <template #clear>
330
321
  <div
331
- v-if="(Array.isArray(value) ? value.length > 0 : !!value) && !disabled && clearable"
322
+ v-if="hasValue && !disabled && clearable"
332
323
  class="multiselect-clear cursor-pointer"
333
324
  @mousedown.prevent.stop="value = null"
334
325
  >
335
- <VIcon class="icon" size="19">
336
- tabler-x
337
- </VIcon>
326
+ <VIcon
327
+ icon="tabler-x"
328
+ class="icon"
329
+ size="19"
330
+ :color="hasError(isValid.value) ? 'error' : ''"
331
+ />
338
332
  </div>
339
333
  </template>
340
334
 
@@ -348,9 +342,7 @@ defineExpose({
348
342
 
349
343
  <template v-if="isLoading" #beforeList>
350
344
  <div class="app-h-50px w-100">
351
- <LoadingBar
352
- :is-loading="isLoading"
353
- />
345
+ <LoadingBar :is-loading="isLoading" />
354
346
  </div>
355
347
  </template>
356
348
 
@@ -383,46 +375,30 @@ defineExpose({
383
375
  validate-on="input lazy"
384
376
  >
385
377
  <div class="d-flex h-100 position-relative">
386
- <div v-if="isOpen" class="w-100 h-100 position-absolute app-z-11" @click="closeMenu" />
378
+ <div v-if="isOpen && !teleported" class="w-100 h-100 position-absolute app-z-11" @click="closeMenu" />
387
379
  <MultiSelect
388
380
  :ref="el => inputRef[`select-input-${elementId}`] = el"
389
381
  v-model="value"
390
- :options="filteredOptions"
391
- :multiple="multiple"
392
- :close-on-select="multiple ? false : true"
393
- :searchable="searchable"
394
- :internal-search="false"
395
- :preselect-first="preselectFirst"
396
- :disabled="disabled || disableIfNoOptions"
397
- :required="required"
398
- :group-select="grouped"
399
- :group-values="groupValues"
400
- :group-label="groupLabel"
382
+ v-bind="sharedMultiSelectProps"
401
383
  class="app-custom-input"
402
384
  :class="{
403
- 'app-custom-input-error': error || isValid.value === false || errorState,
385
+ 'app-custom-input-error': hasError(isValid.value),
404
386
  'app-teleport-clone': teleported
405
387
  }"
406
388
  style="margin-top: 2px;"
407
- :label="itemLabel"
408
- :track-by="trackBy"
409
- :placeholder="value && value?.length > 0 ? '' : internalPlaceholder || t('inputs.placeholder_select')"
410
- :show-labels="false"
411
- :clear-on-select="true"
412
- :show-no-options="isLoading ? false : true"
413
- :show-no-results="isLoading ? false : true"
414
- :deselect-label="deselectLabel"
415
- :hide-selected="hideSelected"
416
- :select-label="selectLabel"
417
- :selected-label="selectedLabel"
418
- @open="showTeleport = true, isOpen = true"
419
- @close="showTeleport = false, isOpen = false"
420
- @search-change="searchInternally ? customSearch($event) : fetchAndUpdateRecords($event)"
421
- @select="errorState = false, resetTeleportState()"
389
+ @open="handleOpen"
390
+ @close="handleClose"
391
+ @search-change="handleSearchChange"
392
+ @select="handleSelect"
422
393
  >
423
394
  <template #caret>
424
395
  <span class="multiselect-arrow">
425
- <VIcon class="icon" size="19">tabler-chevron-down</VIcon>
396
+ <VIcon
397
+ icon="tabler-chevron-down"
398
+ class="icon"
399
+ size="19"
400
+ :color="hasError(isValid.value) ? 'error' : ''"
401
+ />
426
402
  </span>
427
403
  </template>
428
404
 
@@ -460,13 +436,16 @@ defineExpose({
460
436
 
461
437
  <template #clear>
462
438
  <div
463
- v-if="(Array.isArray(value) ? value.length > 0 : !!value) && !disabled && clearable"
439
+ v-if="hasValue && !disabled && clearable"
464
440
  class="multiselect-clear cursor-pointer"
465
441
  @mousedown.prevent.stop="value = null"
466
442
  >
467
- <VIcon class="icon" size="19">
468
- tabler-x
469
- </VIcon>
443
+ <VIcon
444
+ icon="tabler-x"
445
+ class="icon"
446
+ size="19"
447
+ :color="hasError(isValid.value) ? 'error' : ''"
448
+ />
470
449
  </div>
471
450
  </template>
472
451
 
@@ -480,9 +459,7 @@ defineExpose({
480
459
 
481
460
  <template v-if="isLoading" #beforeList>
482
461
  <div class="app-h-50px w-100">
483
- <LoadingBar
484
- :is-loading="isLoading"
485
- />
462
+ <LoadingBar :is-loading="isLoading" />
486
463
  </div>
487
464
  </template>
488
465
 
@@ -515,16 +492,22 @@ defineExpose({
515
492
  <style>
516
493
  .app-custom-input-teleport .multiselect__tags {
517
494
  display: none !important;
495
+ height: 100%;
496
+ display: flex;
497
+ align-items: center;
498
+ cursor: pointer;
499
+ padding-top: 0;
518
500
  }
519
501
  .app-custom-input-teleport .multiselect-arrow {
520
502
  display: none !important;
521
503
  }
522
504
  .app-custom-input-teleport .multiselect__placeholder {
523
- display: none !important;
505
+ margin: 0;
506
+ padding-top: 0;
524
507
  }
525
508
  .app-custom-input-teleport .multiselect__content-wrapper {
526
509
  margin-top: 53px;
527
- z-index: 9999;
510
+ z-index: 10;
528
511
  display: block !important;
529
512
  }
530
513
 
@@ -561,7 +544,7 @@ defineExpose({
561
544
  font-size: 13px !important;
562
545
  background-color: rgba(var(--v-theme-surface));
563
546
  font-weight: 100 !important;
564
- min-height: 43px;
547
+ min-height: 40px;
565
548
  border-radius: 6px;
566
549
  }
567
550
  .app-custom-input .multiselect__tags:hover {
@@ -35,12 +35,14 @@ type __VLS_Props = {
35
35
  isLoading?: boolean;
36
36
  disableIfEmpty?: boolean;
37
37
  clearable?: boolean;
38
+ showNoOptions?: boolean;
39
+ showNoResults?: boolean;
38
40
  };
39
- declare var __VLS_71: string | number, __VLS_72: any, __VLS_136: string | number, __VLS_137: any;
41
+ declare var __VLS_69: string | number, __VLS_70: any, __VLS_134: string | number, __VLS_135: any;
40
42
  type __VLS_Slots = {} & {
41
- [K in NonNullable<typeof __VLS_71>]?: (props: typeof __VLS_72) => any;
43
+ [K in NonNullable<typeof __VLS_69>]?: (props: typeof __VLS_70) => any;
42
44
  } & {
43
- [K in NonNullable<typeof __VLS_136>]?: (props: typeof __VLS_137) => any;
45
+ [K in NonNullable<typeof __VLS_134>]?: (props: typeof __VLS_135) => any;
44
46
  };
45
47
  declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
46
48
  records: Ref<{
@@ -63,6 +63,18 @@ const handleInput = (e, index) => {
63
63
  updateModelAt(index, value);
64
64
  }
65
65
  };
66
+ const handleKeydown = (e, index) => {
67
+ if (e.key === "Delete" || e.key === "Del" || e.key === "Backspace") {
68
+ e.preventDefault();
69
+ const currentValue = model.value?.[index] ?? "";
70
+ if (!currentValue && index > 0) {
71
+ const prevInput = document.getElementById(`license-${props.customId || ""}-${index - 1}`);
72
+ prevInput?.focus();
73
+ } else if (currentValue) {
74
+ updateModelAt(index, "");
75
+ }
76
+ }
77
+ };
66
78
  const clear = () => {
67
79
  model.value = null;
68
80
  };
@@ -108,6 +120,7 @@ watch(() => model.value, (newVal) => {
108
120
  :maxlength="index === 3 ? 4 : 1"
109
121
  :placeholder="index === 3 ? t('inputs.numbers') : t('inputs.character')"
110
122
  @input="handleInput($event, index)"
123
+ @keydown="handleKeydown($event, index)"
111
124
  >
112
125
  <a
113
126
  v-if="model?.some(Boolean)"
@@ -127,8 +140,8 @@ watch(() => model.value, (newVal) => {
127
140
  v-if="!hideDetails"
128
141
  class="mt-1"
129
142
  color="error"
130
- :messages="errorMessages || validationErrors.value"
131
- :active="!isValid.value"
143
+ :messages="[...validationErrors.value, errorMessages]"
144
+ :active="isValid.value === false || errorMessages?.length !== 0"
132
145
  />
133
146
  </div>
134
147
  </VValidation>
@@ -246,7 +246,7 @@ defineExpose({
246
246
 
247
247
  <template #item.created_at="{ item }">
248
248
  <div class="d-flex text-pre-line">
249
- {{ formatDate(item.raw?.created_at) }}
249
+ {{ formatDate(item?.created_at) }}
250
250
  </div>
251
251
  </template>
252
252
 
@@ -34,7 +34,8 @@ const moduleIdentifier = computed(() => props.module ?? "rows");
34
34
  item-value="id"
35
35
  class="secondary-table"
36
36
  >
37
- <template v-for="header in headers" #[`column.${header.key}`] :key="`header.${header.key}`">
37
+ <!-- headers -->
38
+ <template v-for="header in headers" #[`header.${header.key}`] :key="`header.${header.key}`">
38
39
  <div class="d-flex align-center gap-1">
39
40
  {{ header.title }}
40
41
 
@@ -42,7 +43,7 @@ const moduleIdentifier = computed(() => props.module ?? "rows");
42
43
 
43
44
  <VTooltip
44
45
  v-if="header.tooltip"
45
- content-class="bg-secondary !app-max-w-200px text-center app-font-size-12 text-pre-line"
46
+ :content-class="`text-center text-pre-line ${header.tooltipClass}`"
46
47
  >
47
48
  <template #activator="{ props: tooltipProps }">
48
49
  <div class="position-relative">
@@ -62,28 +63,12 @@ const moduleIdentifier = computed(() => props.module ?? "rows");
62
63
  </div>
63
64
  </template>
64
65
 
66
+ <!-- rows -->
65
67
  <template
66
68
  v-for="(header) in headers"
67
- #[`item.${header.key}`]="{ item, index }"
68
- :key="`row.${index}.${header.key}`"
69
+ #[`item.${header.key}`]="{ index }"
70
+ :key="`row.item.${index}.${header.key}`"
69
71
  >
70
- <span
71
- v-if="header.key === 'index'"
72
- :key="`row.${index}.${header.key}`"
73
- class="px-2 d-flex align-center justify-center h-100 app-border-silver"
74
- style="border-inline-start: 0px !important;"
75
- >
76
- {{ `${index + 1 < 10 ? "0" : ""}${index + 1}` }}
77
- </span>
78
-
79
- <span
80
- v-if="header.type === 'display'"
81
- :key="`row.${index}.${header.key}`"
82
- class="px-2 d-flex align-center justify-center h-100 app-border-silver"
83
- >
84
- {{ item[header.key] }}
85
- </span>
86
-
87
72
  <!-- eslint-disable vue/no-mutating-props -->
88
73
  <template v-if="header?.fields && header?.fields?.length > 0">
89
74
  <div
@@ -120,6 +105,7 @@ const moduleIdentifier = computed(() => props.module ?? "rows");
120
105
  <!-- eslint-enable vue/no-mutating-props -->
121
106
  </template>
122
107
 
108
+ <!-- other slots -->
123
109
  <template v-for="(_, name) in $slots" #[name]="slotProps">
124
110
  <slot :name="name" v-bind="slotProps || {}" />
125
111
  </template>
@@ -136,8 +122,8 @@ const moduleIdentifier = computed(() => props.module ?? "rows");
136
122
  border-bottom: 0px solid rgba(var(--v-table-divider)) !important;
137
123
  }
138
124
 
139
- .v-table__wrapper {
140
- overflow: inherit !important;
125
+ :deep(.v-table__wrapper) {
126
+ overflow: visible !important;
141
127
  }
142
128
 
143
129
  table tr th {
@@ -150,17 +136,15 @@ table tr th {
150
136
 
151
137
  .secondary-table :deep(table) {
152
138
  width: 100% !important;
153
- border-collapse: collapse !important;
154
139
  border-spacing: 0 !important;
155
140
  border: 0px solid rgb(var(--v-theme-silver)) !important;
156
- border-collapse: collapse !important;
157
141
  font-size: 13px !important;
158
142
  }
159
143
  .secondary-table :deep(table) thead th {
160
144
  border-top: 1px solid rgb(var(--v-theme-silver)) !important;
161
145
  border-left: 1px solid rgb(var(--v-theme-silver)) !important;
162
146
  border-right: 1px solid rgb(var(--v-theme-silver)) !important;
163
- border-bottom: 0px !important;
147
+ border-bottom: 0px solid rgb(var(--v-theme-silver)) !important;
164
148
  }
165
149
  .secondary-table :deep(table) tbody tr:first-child {
166
150
  display: none !important;
@@ -172,15 +156,15 @@ table tr th {
172
156
  padding: 14px 16px !important;
173
157
  height: fit-content !important;
174
158
  top: 80px !important;
175
- border: 0px solid rgb(var(--v-theme-silver)) !important;
159
+ border: 1px solid rgb(var(--v-theme-silver)) !important;
176
160
  background-color: rgb(var(--v-theme-gray-200)) !important;
177
161
  color: rgb(var(--v-theme-on-surface)) !important;
178
162
  }
179
163
  .secondary-table :deep(table) td:first-child {
180
- border-inline-start: 1px solid rgb(var(--v-theme-silver)) !important;
164
+ border: 1px solid rgb(var(--v-theme-silver)) !important;
181
165
  }
182
166
  .secondary-table :deep(table) td:last-child {
183
- border-top: 0px solid rgb(var(--v-theme-silver)) !important;
167
+ border: 1px solid rgb(var(--v-theme-silver)) !important;
184
168
  }
185
169
  .secondary-table :deep(table) td {
186
170
  padding: 0px !important;
@@ -264,7 +248,6 @@ table tr th {
264
248
  height: 100%;
265
249
  border-color: rgba(var(--v-theme-silver));
266
250
  border-radius: 0;
267
- padding-top: 0px !important;
268
251
  }
269
252
  :deep(.app-custom-input) .multiselect__tags .multiselect__single {
270
253
  margin-bottom: 0px;
@@ -312,11 +295,6 @@ table tr th {
312
295
  --v-field-border-opacity: 1 !important;
313
296
  }
314
297
 
315
- .app-input-table {
316
- min-width: 250px !important;
317
- max-width: 250px !important;
318
- }
319
-
320
298
  .app-custom-input-teleport .multiselect__tags {
321
299
  border-color: rgba(var(--v-theme-silver)) !important;
322
300
  border-radius: 0;
@@ -101,6 +101,7 @@ const placeholder = (header) => {
101
101
  flat
102
102
  hide-details
103
103
  />
104
+
104
105
  <AppNumberField
105
106
  v-if="header.type === 'number'"
106
107
  v-model="item[header.key]"
@@ -158,7 +159,6 @@ const placeholder = (header) => {
158
159
  :error-messages="errorMessages"
159
160
  :error="error"
160
161
  :disable-if-empty="header.disableIfEmpty"
161
- teleported
162
162
  hide-details
163
163
  :clearable="header.clearable"
164
164
  />
@@ -185,7 +185,6 @@ const placeholder = (header) => {
185
185
  :error="error"
186
186
  :disable-if-empty="header.disableIfEmpty"
187
187
  grouped
188
- teleported
189
188
  hide-details
190
189
  :clearable="header.clearable"
191
190
  />
@@ -249,4 +248,20 @@ const placeholder = (header) => {
249
248
  :error-messages="errorMessages"
250
249
  hide-details
251
250
  />
251
+
252
+ <span
253
+ v-if="header.key === 'index'"
254
+ :key="`row.${index}.${header.key}`"
255
+ class="px-2 d-flex align-center justify-center h-100"
256
+ >
257
+ {{ `${index + 1 < 10 ? "0" : ""}${index + 1}` }}
258
+ </span>
259
+
260
+ <span
261
+ v-if="header.type === 'display'"
262
+ :key="`row.${index}.${header.key}`"
263
+ class="px-2 d-flex align-center justify-center h-100"
264
+ >
265
+ {{ item[header.key] }}
266
+ </span>
252
267
  </template>
@@ -626,7 +626,7 @@ $btn-hover-overlay-opacity: (
626
626
 
627
627
  &:not(.v-input--disabled) .v-switch__track {
628
628
  border: 1px solid rgba(var(--v-border-color), var(--v-switch-opacity));
629
- background-color: rgb(var(--v-theme-surface));
629
+ background-color: rgba(var(--v-theme-on-surface),var(--v-focus-opacity));
630
630
  opacity: 1;
631
631
  }
632
632
 
@@ -636,9 +636,7 @@ $btn-hover-overlay-opacity: (
636
636
  --v-selection-control-size: 1.5rem;
637
637
 
638
638
  .v-switch__thumb {
639
- background: rgb(var(--v-theme-on-success));
640
- // block-size: 1rem;
641
- // inline-size: 1rem;
639
+ background: rgb(var(--v-theme-on-success)) !important;
642
640
  }
643
641
  }
644
642
 
@@ -1042,3 +1040,13 @@ $btn-hover-overlay-opacity: (
1042
1040
  ::-webkit-resizer {
1043
1041
  background: transparent;
1044
1042
  }
1043
+
1044
+
1045
+ .v-tooltip > .v-overlay__content {
1046
+ color: rgb(var(--v-theme-on-secondary)) !important;
1047
+ font-size: 13px !important;
1048
+ }
1049
+
1050
+ .v-icon {
1051
+ outline: none !important;
1052
+ }
@@ -18397,7 +18397,7 @@ body {
18397
18397
  }
18398
18398
  .v-switch.v-switch--inset:not(.v-input--disabled) .v-switch__track {
18399
18399
  border: 1px solid rgba(var(--v-border-color), var(--v-switch-opacity));
18400
- background-color: rgb(var(--v-theme-surface));
18400
+ background-color: rgba(var(--v-theme-on-surface), var(--v-focus-opacity));
18401
18401
  opacity: 1;
18402
18402
  }
18403
18403
  .v-switch.v-switch--inset .v-selection-control__input {
@@ -18405,7 +18405,7 @@ body {
18405
18405
  --v-selection-control-size: 1.5rem;
18406
18406
  }
18407
18407
  .v-switch.v-switch--inset .v-selection-control__input .v-switch__thumb {
18408
- background: rgb(var(--v-theme-on-success));
18408
+ background: rgb(var(--v-theme-on-success)) !important;
18409
18409
  }
18410
18410
  .v-switch.v-switch--inset .v-selection-control--dirty .v-switch__track {
18411
18411
  border-color: rgba(var(--v-border-color), var(--v-switch-opacity));
@@ -18768,6 +18768,15 @@ body {
18768
18768
  background: transparent;
18769
18769
  }
18770
18770
 
18771
+ .v-tooltip > .v-overlay__content {
18772
+ color: rgb(var(--v-theme-on-secondary)) !important;
18773
+ font-size: 13px !important;
18774
+ }
18775
+
18776
+ .v-icon {
18777
+ outline: none !important;
18778
+ }
18779
+
18771
18780
  .v-timeline-item .app-timeline-title {
18772
18781
  color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
18773
18782
  font-size: 15px;
package/dist/types.d.ts CHANGED
@@ -124,6 +124,7 @@ export type EditableDataTableItem = {
124
124
  renderDataOnBeforeMount?: boolean
125
125
  showDescription?: boolean
126
126
  tooltip?: string
127
+ tooltipClass?: string
127
128
  clearable?: boolean
128
129
  disabled?: boolean
129
130
  maxlength?: number
@@ -4,4 +4,5 @@ export declare function formValidator(errors: ErrorResponseFiled[] | undefined,
4
4
  getMessage: () => string;
5
5
  hasError: () => boolean;
6
6
  };
7
- export declare function getErrorMessage(errors: ErrorResponseFiled[] | undefined, key: string): string;
7
+ export declare function getErrorMessage(errors: ErrorResponseFiled[] | undefined, key: string): string | undefined;
8
+ export declare function getErrorMessages(errors: ErrorResponseFiled[] | undefined, keys: string[]): string | undefined;
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.formValidator = formValidator;
7
7
  exports.getErrorMessage = getErrorMessage;
8
+ exports.getErrorMessages = getErrorMessages;
8
9
  function formValidator(errors = [], key) {
9
10
  const getError = () => errors?.find(e => e.field == key);
10
11
  const getMessage = () => getError()?.message || "";
@@ -16,5 +17,14 @@ function formValidator(errors = [], key) {
16
17
  };
17
18
  }
18
19
  function getErrorMessage(errors = [], key) {
19
- return errors?.find(e => e.field == key)?.message || "";
20
+ return errors?.find(e => e.field == key)?.message || void 0;
21
+ }
22
+ function getErrorMessages(errors = [], keys) {
23
+ for (const key of keys) {
24
+ const error = errors?.find(e => e.field == key);
25
+ if (error?.message) {
26
+ return error.message;
27
+ }
28
+ }
29
+ return void 0;
20
30
  }
@@ -9,5 +9,14 @@ export function formValidator(errors = [], key) {
9
9
  };
10
10
  }
11
11
  export function getErrorMessage(errors = [], key) {
12
- return errors?.find((e) => e.field == key)?.message || "";
12
+ return errors?.find((e) => e.field == key)?.message || void 0;
13
+ }
14
+ export function getErrorMessages(errors = [], keys) {
15
+ for (const key of keys) {
16
+ const error = errors?.find((e) => e.field == key);
17
+ if (error?.message) {
18
+ return error.message;
19
+ }
20
+ }
21
+ return void 0;
13
22
  }
@@ -7,3 +7,5 @@ export declare const buildFormData: <T>(data: T | T[], excludedKeys?: string[],
7
7
  export declare const stripLeadingZero: (mobile: string) => string;
8
8
  export declare const cloneObjectDeep: <T>(obj: T) => T;
9
9
  export declare const mergeDeep: (target: any, source: any) => any;
10
+ export declare const normalizeArabicText: (text: string | number) => string;
11
+ export declare const searchArabicText: (value: string | number, query: string) => boolean;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.stripLeadingZero = exports.mergeDeep = exports.isNullOrUndefined = exports.isEmptyArray = exports.isEmpty = exports.generateUniqueId = exports.generateRandomId = exports.cloneObjectDeep = exports.buildFormData = void 0;
6
+ exports.stripLeadingZero = exports.searchArabicText = exports.normalizeArabicText = exports.mergeDeep = exports.isNullOrUndefined = exports.isEmptyArray = exports.isEmpty = exports.generateUniqueId = exports.generateRandomId = exports.cloneObjectDeep = exports.buildFormData = void 0;
7
7
  const generateRandomId = length => {
8
8
  const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
9
9
  let result = "";
@@ -81,4 +81,20 @@ const mergeDeep = (target, source) => {
81
81
  }
82
82
  return output;
83
83
  };
84
- exports.mergeDeep = mergeDeep;
84
+ exports.mergeDeep = mergeDeep;
85
+ const normalizeArabicText = text => {
86
+ if (typeof text === "number") {
87
+ return text.toString();
88
+ }
89
+ return text.trim().replace(/أ|ا|إ|آ/g, "\u0627").replace(/ي|ى|ئ/g, "\u064A").replace(/ة/g, "\u0647");
90
+ };
91
+ exports.normalizeArabicText = normalizeArabicText;
92
+ const searchArabicText = (value, query) => {
93
+ if (!value || !query) {
94
+ return false;
95
+ }
96
+ const normalizedValue = normalizeArabicText(value.toString()).toLowerCase();
97
+ const normalizedQuery = normalizeArabicText(query).toLowerCase();
98
+ return normalizedValue.includes(normalizedQuery);
99
+ };
100
+ exports.searchArabicText = searchArabicText;
@@ -65,3 +65,17 @@ export const mergeDeep = (target, source) => {
65
65
  }
66
66
  return output;
67
67
  };
68
+ export const normalizeArabicText = (text) => {
69
+ if (typeof text === "number") {
70
+ return text.toString();
71
+ }
72
+ return text.trim().replace(/أ|ا|إ|آ/g, "\u0627").replace(/ي|ى|ئ/g, "\u064A").replace(/ة/g, "\u0647");
73
+ };
74
+ export const searchArabicText = (value, query) => {
75
+ if (!value || !query) {
76
+ return false;
77
+ }
78
+ const normalizedValue = normalizeArabicText(value.toString()).toLowerCase();
79
+ const normalizedQuery = normalizeArabicText(query).toLowerCase();
80
+ return normalizedValue.includes(normalizedQuery);
81
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@winchsa/ui",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "publishConfig": {