sprintify-ui 0.0.32 → 0.0.33

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.
@@ -7,7 +7,7 @@
7
7
  >
8
8
  <slot
9
9
  name="option"
10
- :is-active="isActive"
10
+ :selected="isSelected(option)"
11
11
  :on-select="onSelect"
12
12
  :option="option"
13
13
  >
@@ -16,7 +16,7 @@
16
16
  :disabled="disabled"
17
17
  :class="[
18
18
  buttonClass,
19
- isActive(option) ? buttonActiveClass : buttonInactiveClass,
19
+ isSelected(option) ? buttonActiveClass : buttonInactiveClass,
20
20
  ]"
21
21
  @click="onSelect(option)"
22
22
  >
@@ -31,6 +31,7 @@
31
31
  import { PropType } from 'vue';
32
32
  import { NormalizedOption, Option } from '@/types/types';
33
33
  import { cloneDeep, isArray, isObject } from 'lodash';
34
+ import { useHasOptions } from '@/composables/hasOptions';
34
35
 
35
36
  const props = defineProps({
36
37
  modelValue: {
@@ -87,57 +88,14 @@ const props = defineProps({
87
88
 
88
89
  const emit = defineEmits(['update:modelValue']);
89
90
 
90
- const normalizedModelValue = computed(
91
- (): NormalizedOption[] | NormalizedOption | null => {
92
- if (props.multiple) {
93
- if (!isArray(props.modelValue)) {
94
- return [];
95
- }
96
- return props.modelValue.map((option) => {
97
- return {
98
- label: option[props.labelKey] as string,
99
- value: option[props.valueKey] as string | number,
100
- option: option,
101
- } as NormalizedOption;
102
- });
103
- } else {
104
- if (!isObject(props.modelValue)) {
105
- return null;
106
- }
107
-
108
- return {
109
- label: props.modelValue[props.labelKey as never] as string,
110
- value: props.modelValue[props.valueKey as never] as string | number,
111
- option: props.modelValue,
112
- } as NormalizedOption;
113
- }
114
- }
91
+ const { normalizedOptions, normalizedModelValue, isSelected } = useHasOptions(
92
+ computed(() => props.modelValue),
93
+ computed(() => props.options),
94
+ computed(() => props.labelKey),
95
+ computed(() => props.valueKey),
96
+ computed(() => props.multiple)
115
97
  );
116
98
 
117
- const normalizedOptions = computed((): NormalizedOption[] => {
118
- return props.options.map((option) => {
119
- return {
120
- label: option[props.labelKey] as string,
121
- value: option[props.valueKey] as string | number,
122
- option: option,
123
- } as NormalizedOption;
124
- });
125
- });
126
-
127
- function isActive(option: NormalizedOption): boolean {
128
- if (isArray(normalizedModelValue.value)) {
129
- return normalizedModelValue.value.some((modelValue) => {
130
- return modelValue.value === option.value;
131
- });
132
- }
133
-
134
- if (isObject(normalizedModelValue.value)) {
135
- return normalizedModelValue.value.value == option.value;
136
- }
137
-
138
- return false;
139
- }
140
-
141
99
  function onSelect(option: NormalizedOption) {
142
100
  if (props.multiple) {
143
101
  let newModalValue = [] as NormalizedOption[];
@@ -64,16 +64,14 @@ export const SlotOption = (args) => ({
64
64
  v-model="value"
65
65
  :options="options"
66
66
  >
67
- <template #option="{ option, active, selected }">
67
+ <template #option="{ option, active }">
68
68
  <div
69
69
  class="rounded px-2 font-semibold py-1 text-sm"
70
70
  :class="{
71
- 'hover:bg-slate-100': !active && !selected,
72
- 'bg-slate-200 hover:bg-slate-300': active && !selected,
73
- 'bg-blue-500 text-white hover:bg-blue-600': !active && selected,
74
- 'bg-blue-600 text-white hover:bg-blue-700': active && selected,
71
+ 'hover:bg-slate-100': !active,
72
+ 'bg-slate-200 hover:bg-slate-300': active,
75
73
  }"
76
- :style="{ color: selected ? '' : option.value }"
74
+ :style="{ color: option.value }"
77
75
  >
78
76
  {{ option.label }}
79
77
  </div>
@@ -82,7 +82,6 @@
82
82
  <slot
83
83
  name="option"
84
84
  :option="option.option"
85
- :selected="optionValues.includes(option.value)"
86
85
  :active="optionActive && optionActive.value == option.value"
87
86
  >
88
87
  <div
@@ -127,10 +126,11 @@
127
126
  <script lang="ts" setup>
128
127
  import { cloneDeep, get } from 'lodash';
129
128
  import { PropType, Ref, ComputedRef } from 'vue';
130
- import { NormalizedOption, Option, OptionValue } from '@/types/types';
129
+ import { NormalizedOption, Option } from '@/types/types';
131
130
  import { useInfiniteScroll, useMutationObserver } from '@vueuse/core';
132
131
  import { useNotificationsStore } from '@/stores/notifications';
133
132
  import BaseSkeleton from './BaseSkeleton.vue';
133
+ import { useHasOptions } from '@/composables/hasOptions';
134
134
 
135
135
  const props = defineProps({
136
136
  modelValue: {
@@ -194,9 +194,23 @@ const keywords = ref('');
194
194
  const showDropdown = ref(false);
195
195
  const inputElement = ref(null) as Ref<HTMLInputElement | null>;
196
196
  const dropdown = ref(null) as Ref<HTMLElement | null>;
197
- const selectionIndex = ref(0);
197
+ const activeIndex = ref(0);
198
198
  const selectionToDelete = ref(null) as Ref<NormalizedOption | null>;
199
199
 
200
+ const hasOptions = useHasOptions(
201
+ computed(() => props.modelValue),
202
+ computed(() => props.options),
203
+ computed(() => props.labelKey),
204
+ computed(() => props.valueKey),
205
+ computed(() => true)
206
+ );
207
+
208
+ const isSelected = hasOptions.isSelected;
209
+ const normalizedOptions = hasOptions.normalizedOptions;
210
+ const normalizedModelValue = hasOptions.normalizedModelValue as ComputedRef<
211
+ NormalizedOption[]
212
+ >;
213
+
200
214
  onMounted(() => {
201
215
  useInfiniteScroll(
202
216
  dropdown.value,
@@ -210,34 +224,11 @@ onMounted(() => {
210
224
  const optionActive = computed(() => {
211
225
  return (
212
226
  filteredNormalizedOptions.value[
213
- Math.min(selectionIndex.value, filteredNormalizedOptions.value.length - 1)
227
+ Math.min(activeIndex.value, filteredNormalizedOptions.value.length - 1)
214
228
  ] ?? null
215
229
  );
216
230
  });
217
231
 
218
- const normalizedModelValue = computed(() => {
219
- if (!props.modelValue) {
220
- return [];
221
- }
222
- return props.modelValue.map((o) => {
223
- return {
224
- label: o[props.labelKey] as string,
225
- value: o[props.valueKey] as string | number,
226
- option: o,
227
- };
228
- });
229
- }) as ComputedRef<NormalizedOption[]>;
230
-
231
- const normalizedOptions = computed(() => {
232
- return props.options.map((option) => {
233
- return {
234
- label: option[props.labelKey] as string,
235
- value: option[props.valueKey] as string | number,
236
- option: option,
237
- } as NormalizedOption;
238
- });
239
- });
240
-
241
232
  const filteredNormalizedOptions = computed((): NormalizedOption[] => {
242
233
  return normalizedOptions.value
243
234
  .filter((option) => {
@@ -250,20 +241,10 @@ const filteredNormalizedOptions = computed((): NormalizedOption[] => {
250
241
  return option.label.toLowerCase().includes(keywords.value.toLowerCase());
251
242
  })
252
243
  .filter((option) => {
253
- return !hasSelectedOption(option.value);
244
+ return !isSelected(option);
254
245
  });
255
246
  });
256
247
 
257
- const optionValues = computed(() => {
258
- return normalizedModelValue.value.map((o) => {
259
- return o.value;
260
- });
261
- });
262
-
263
- const hasSelectedOption = (value: OptionValue): boolean => {
264
- return optionValues.value.includes(value);
265
- };
266
-
267
248
  function preventUnfocus(elements: HTMLElement[]) {
268
249
  elements.forEach((e) => {
269
250
  e.removeEventListener('mousedown', dontLooseFocus);
@@ -294,7 +275,7 @@ const onTextBlur = () => {
294
275
  };
295
276
 
296
277
  const onTextInput = (event: Event) => {
297
- selectionIndex.value = 0;
278
+ activeIndex.value = 0;
298
279
  selectionToDelete.value = null;
299
280
  setKeywords(get(event, 'target.value', '') + '');
300
281
  dropdown.value?.scrollTo({
@@ -315,19 +296,22 @@ const onTextKeydown = (event: KeyboardEvent) => {
315
296
  }
316
297
 
317
298
  if (key === 'ArrowDown') {
318
- if (selectionIndex.value < props.options.length - 1) {
319
- selectionIndex.value++;
299
+ if (activeIndex.value < filteredNormalizedOptions.value.length - 1) {
300
+ activeIndex.value++;
320
301
  } else {
321
- selectionIndex.value = 0;
302
+ activeIndex.value = 0;
322
303
  }
323
304
  return;
324
305
  }
325
306
 
326
307
  if (key === 'ArrowUp') {
327
- if (selectionIndex.value > 0) {
328
- selectionIndex.value--;
308
+ if (activeIndex.value > 0) {
309
+ activeIndex.value--;
329
310
  } else {
330
- selectionIndex.value = Math.max(0, props.options.length - 1);
311
+ activeIndex.value = Math.max(
312
+ 0,
313
+ filteredNormalizedOptions.value.length - 1
314
+ );
331
315
  }
332
316
  return;
333
317
  }
@@ -343,9 +327,7 @@ const onTextKeydown = (event: KeyboardEvent) => {
343
327
 
344
328
  const optionClass = (option: NormalizedOption) => {
345
329
  const active = optionActive.value && optionActive.value.value == option.value;
346
- const selected =
347
- normalizedModelValue.value &&
348
- normalizedModelValue.value.map((o) => o.value).includes(option.value);
330
+ const selected = isSelected(option);
349
331
 
350
332
  if (selected) {
351
333
  if (active) {
@@ -389,7 +371,7 @@ const addOption = (option: NormalizedOption) => {
389
371
  return;
390
372
  }
391
373
 
392
- if (hasSelectedOption(option.value)) {
374
+ if (isSelected(option)) {
393
375
  return;
394
376
  }
395
377
 
@@ -440,19 +422,19 @@ const beforeAddOption = () => {
440
422
  };
441
423
 
442
424
  const afterUpdate = () => {
443
- validateSelectionIndex();
425
+ validateActiveIndex();
444
426
  };
445
427
 
446
428
  const clearInput = () => {
447
429
  setKeywords('');
448
430
  };
449
431
 
450
- const validateSelectionIndex = () => {
432
+ const validateActiveIndex = () => {
451
433
  // Wait for filteredOptions to update
452
434
  nextTick(() => {
453
- selectionIndex.value = Math.max(
435
+ activeIndex.value = Math.max(
454
436
  0,
455
- Math.min(selectionIndex.value, normalizedOptions.value.length - 1)
437
+ Math.min(activeIndex.value, filteredNormalizedOptions.value.length - 1)
456
438
  );
457
439
  });
458
440
  };
@@ -11,6 +11,7 @@ import BaseBelongsTo from './BaseBelongsTo.vue';
11
11
  import BaseBoolean from './BaseBoolean.vue';
12
12
  import BaseBreadcrumbs from './BaseBreadcrumbs.vue';
13
13
  import BaseButton from './BaseButton.vue';
14
+ import BaseButtonGroup from './BaseButtonGroup.vue';
14
15
  import BaseCard from './BaseCard.vue';
15
16
  import BaseCardRow from './BaseCardRow.vue';
16
17
  import BaseCharacterCounter from './BaseCharacterCounter.vue';
@@ -80,6 +81,7 @@ export {
80
81
  BaseBoolean,
81
82
  BaseBreadcrumbs,
82
83
  BaseButton,
84
+ BaseButtonGroup,
83
85
  BaseCard,
84
86
  BaseCardRow,
85
87
  BaseCharacterCounter,
@@ -0,0 +1,68 @@
1
+ import { Ref } from 'vue';
2
+ import { NormalizedOption, Option } from '@/types/types';
3
+ import { isArray, isObject } from 'lodash';
4
+
5
+ export function useHasOptions(
6
+ modelValue: Ref<Option[] | Option | null | undefined>,
7
+ options: Ref<Option[]>,
8
+ labelKey: Ref<string>,
9
+ valueKey: Ref<string>,
10
+ multiple: Ref<boolean> = ref(false)
11
+ ) {
12
+ const normalizedModelValue = computed(
13
+ (): NormalizedOption[] | NormalizedOption | null => {
14
+ if (multiple.value) {
15
+ if (!isArray(modelValue.value)) {
16
+ return [];
17
+ }
18
+ return modelValue.value.map((option) => {
19
+ return {
20
+ label: option[labelKey.value] as string,
21
+ value: option[valueKey.value] as string | number,
22
+ option: option,
23
+ } as NormalizedOption;
24
+ });
25
+ } else {
26
+ if (!isObject(modelValue.value)) {
27
+ return null;
28
+ }
29
+
30
+ return {
31
+ label: modelValue.value[labelKey.value as never] as string,
32
+ value: modelValue.value[valueKey.value as never] as string | number,
33
+ option: modelValue.value,
34
+ } as NormalizedOption;
35
+ }
36
+ }
37
+ );
38
+
39
+ const normalizedOptions = computed((): NormalizedOption[] => {
40
+ return options.value.map((option) => {
41
+ return {
42
+ label: option[labelKey.value] as string,
43
+ value: option[valueKey.value] as string | number,
44
+ option: option,
45
+ } as NormalizedOption;
46
+ });
47
+ });
48
+
49
+ function isSelected(option: NormalizedOption): boolean {
50
+ if (isArray(normalizedModelValue.value)) {
51
+ return normalizedModelValue.value.some((modelValue) => {
52
+ return modelValue.value === option.value;
53
+ });
54
+ }
55
+
56
+ if (isObject(normalizedModelValue.value)) {
57
+ return normalizedModelValue.value.value == option.value;
58
+ }
59
+
60
+ return false;
61
+ }
62
+
63
+ return {
64
+ normalizedOptions,
65
+ normalizedModelValue,
66
+ isSelected,
67
+ };
68
+ }
@@ -21,16 +21,12 @@ export type OptionValue = string | number;
21
21
 
22
22
  export type Option = Record<string, any>;
23
23
 
24
- export type Selection = Option | null | undefined;
25
-
26
24
  export type NormalizedOption = {
27
25
  option: Option;
28
26
  value: OptionValue;
29
27
  label: string;
30
28
  };
31
29
 
32
- export type NormalizedSelection = NormalizedOption | null | undefined;
33
-
34
30
  export type MediaLibraryPayload = {
35
31
  to_remove: string[];
36
32
  to_add: UploadedFile[];