sprintify-ui 0.6.72 → 0.6.74

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.
@@ -38,8 +38,8 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
38
38
  type: StringConstructor;
39
39
  };
40
40
  currentModel: {
41
- default: null;
42
- type: PropType<Option | null>;
41
+ default: undefined;
42
+ type: PropType<Option | null | undefined>;
43
43
  };
44
44
  hasError: {
45
45
  default: boolean;
@@ -127,8 +127,8 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
127
127
  type: StringConstructor;
128
128
  };
129
129
  currentModel: {
130
- default: null;
131
- type: PropType<Option | null>;
130
+ default: undefined;
131
+ type: PropType<Option | null | undefined>;
132
132
  };
133
133
  hasError: {
134
134
  default: boolean;
@@ -190,7 +190,7 @@ declare const _default: __VLS_WithTemplateSlots<import("vue").DefineComponent<{
190
190
  emptyOptionLabel: string;
191
191
  primaryKey: string;
192
192
  showRouteUrl: ((id: string | number) => string) | undefined;
193
- currentModel: Option | null;
193
+ currentModel: Option | null | undefined;
194
194
  }, {}>, {
195
195
  option?(_: {
196
196
  focus: () => void;
@@ -1,6 +1,7 @@
1
1
  import { PropType } from 'vue';
2
2
  declare function focusAction(): void;
3
3
  declare function blurAction(): void;
4
+ declare function selectAction(): void;
4
5
  declare const _default: import("vue").DefineComponent<{
5
6
  modelValue: {
6
7
  default: string;
@@ -96,6 +97,7 @@ declare const _default: import("vue").DefineComponent<{
96
97
  }, {
97
98
  focus: typeof focusAction;
98
99
  blur: typeof blurAction;
100
+ select: typeof selectAction;
99
101
  }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
100
102
  click: (...args: any[]) => void;
101
103
  blur: (...args: any[]) => void;
@@ -0,0 +1,2 @@
1
+ import { Collection, PaginatedCollection, ResourceCollection } from "@/types";
2
+ export declare function getItems(data: Collection | PaginatedCollection | ResourceCollection | null): Collection;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprintify-ui",
3
- "version": "0.6.72",
3
+ "version": "0.6.74",
4
4
  "scripts": {
5
5
  "build": "rimraf dist && vue-tsc && vite build",
6
6
  "build-fast": "rimraf dist && vite build",
@@ -107,7 +107,6 @@
107
107
  </template>
108
108
 
109
109
  <script lang="ts" setup>
110
- import { get } from 'lodash';
111
110
  import { PropType, ComputedRef } from 'vue';
112
111
  import { NormalizedOption, Option, OptionValue } from '@/types';
113
112
  import { useHasOptions } from '@/composables/hasOptions';
@@ -15,7 +15,7 @@ export default {
15
15
  },
16
16
  },
17
17
  args: {
18
- url: 'https://effettandem.com/api/content/articles',
18
+ url: 'https://faker.witify.io/api/todos',
19
19
  labelKey: 'title',
20
20
  valueKey: 'id',
21
21
  },
@@ -104,7 +104,7 @@ export const SlotOption = (args) => {
104
104
  }"
105
105
  >
106
106
  <p class="text-sm font-medium">{{ option.title }}</p>
107
- <p class="opacity-60 text-xs">{{ option.owner?.name }}</p>
107
+ <p class="opacity-60 text-xs">{{ option.description }}</p>
108
108
  </div>
109
109
  </template>
110
110
  </BaseAutocompleteFetch>
@@ -172,8 +172,9 @@ export const WithSelect = (args) => {
172
172
  const select = {
173
173
  options: [
174
174
  { label: 'All', value: 'all' },
175
- { label: 'Video', value: 'video' },
176
- { label: 'Article', value: 'article' },
175
+ { label: 'Work', value: 'work' },
176
+ { label: 'Personal', value: 'personal' },
177
+ { label: 'Family', value: 'family' },
177
178
  ],
178
179
  labelKey: 'label',
179
180
  valueKey: 'value',
@@ -184,10 +185,10 @@ export const WithSelect = (args) => {
184
185
 
185
186
  const url = computed(() => {
186
187
  if (selected.value == 'all' || !selected.value) {
187
- return 'https://effettandem.com/api/content/articles';
188
+ return 'https://faker.witify.io/api/todos';
188
189
  }
189
190
  return (
190
- 'https://effettandem.com/api/content/articles' +
191
+ 'https://faker.witify.io/api/todos' +
191
192
  '?type=' +
192
193
  selected.value
193
194
  );
@@ -11,12 +11,16 @@ export default {
11
11
  argTypes: {
12
12
  size: {
13
13
  control: { type: 'select' },
14
+ options: sizes,
14
15
  },
15
- options: sizes,
16
16
  },
17
17
  args: {
18
- url: 'https://effettandem.com/api/content/articles',
19
- field: 'title',
18
+ url: 'https://faker.witify.io/api/todos',
19
+ field: 'name',
20
+ primaryKey: 'uuid',
21
+ showRouteUrl(id) {
22
+ return `https://faker.witify.io/api/todos/${id}`;
23
+ },
20
24
  },
21
25
  decorators: [() => ({ template: '<div class="mb-36"><story/></div>' })],
22
26
  };
@@ -171,8 +175,9 @@ export const WithSelect = (args) => {
171
175
  const select = {
172
176
  options: [
173
177
  { label: 'All', value: 'all' },
174
- { label: 'Video', value: 'video' },
175
- { label: 'Article', value: 'article' },
178
+ { label: 'Personal', value: 'personal' },
179
+ { label: 'Work', value: 'work' },
180
+ { label: 'Family', value: 'family' },
176
181
  ],
177
182
  labelKey: 'label',
178
183
  valueKey: 'value',
@@ -183,10 +188,10 @@ export const WithSelect = (args) => {
183
188
 
184
189
  const url = computed(() => {
185
190
  if (selected.value == 'all' || !selected.value) {
186
- return 'https://effettandem.com/api/content/articles';
191
+ return 'https://faker.witify.io/api/todos';
187
192
  }
188
193
  return (
189
- 'https://effettandem.com/api/content/articles' +
194
+ 'https://faker.witify.io/api/todos' +
190
195
  '?type=' +
191
196
  selected.value
192
197
  );
@@ -49,6 +49,7 @@ import { config } from '@/index';
49
49
  import BaseAutocompleteFetch from './BaseAutocompleteFetch.vue';
50
50
  import { Option, SelectConfiguration } from '@/types';
51
51
  import { Size } from '@/utils/sizes';
52
+ import { isObject } from 'lodash';
52
53
 
53
54
  const props = defineProps({
54
55
  modelValue: {
@@ -90,8 +91,8 @@ const props = defineProps({
90
91
  type: String,
91
92
  },
92
93
  currentModel: {
93
- default: null,
94
- type: [Object, null] as PropType<Option | null>,
94
+ default: undefined,
95
+ type: [Object, null, undefined] as PropType<Option | null | undefined>,
95
96
  },
96
97
  hasError: {
97
98
  default: false,
@@ -156,23 +157,43 @@ watch(
156
157
  watch(
157
158
  () => props.modelValue,
158
159
  (newValue, oldValue) => {
159
- if (!props.modelValue) {
160
- model.value = null;
160
+
161
+ if (props.showRouteUrl == null) {
161
162
  return;
162
163
  }
163
164
 
164
- if (newValue == oldValue) {
165
+ if (props.currentModel !== undefined) {
165
166
  return;
166
167
  }
167
168
 
168
- if (props.showRouteUrl == null) {
169
+ if (!props.modelValue) {
170
+ model.value = null;
171
+ return;
172
+ }
173
+
174
+ if (newValue == oldValue) {
169
175
  return;
170
176
  }
171
177
 
172
178
  http
173
179
  .get(props.showRouteUrl(props.modelValue))
174
180
  .then((response: AxiosResponse) => {
175
- model.value = response.data.data;
181
+
182
+ const data = response.data as Record<string, unknown>;
183
+
184
+ if (!isObject(data)) {
185
+ return;
186
+ }
187
+
188
+ if (data[props.primaryKey as never] == props.modelValue) {
189
+ model.value = data;
190
+ return;
191
+ }
192
+
193
+ if (isObject(data.data) && data.data[props.primaryKey as never] == props.modelValue) {
194
+ model.value = data.data;
195
+ return;
196
+ }
176
197
  })
177
198
  .catch((e: Error) => e);
178
199
  },
@@ -110,12 +110,13 @@ const getErrorMessageByName = inject(
110
110
  }
111
111
  ) as (name: string) => string | null | undefined;
112
112
 
113
- const clearErrors = inject('form:clearErrors', () => {
113
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
114
+ const clearErrors = inject('form:clearErrors', (name?: string | null | undefined) => {
114
115
  return;
115
- }) as () => void;
116
+ }) as (name?: string | null | undefined) => void;
116
117
 
117
- function fieldOnUpdate() {
118
- clearErrors();
118
+ function fieldOnUpdate(name?: string | null | undefined) {
119
+ clearErrors(name);
119
120
  }
120
121
 
121
122
  const labelNormalized = computed((): string | null => {
@@ -5,16 +5,6 @@ import ShowValue from '@/../.storybook/components/ShowValue.vue';
5
5
  export default {
6
6
  title: 'Form/BaseFieldI18n',
7
7
  component: BaseFieldI18n,
8
- decorators: [
9
- (story) => ({
10
- components: { story, BaseForm },
11
- template: `
12
- <BaseForm method="post" url="https://faker.witify.io/api/todos/422" :data="{}">
13
- <story/>
14
- <button type="submit" class="btn btn-primary mt-5">Submit</button>
15
- </BaseForm>`,
16
- }),
17
- ],
18
8
  args: {
19
9
  component: 'BaseInput',
20
10
  required: true,
@@ -24,14 +14,23 @@ export default {
24
14
  };
25
15
 
26
16
  const Template = (args) => ({
27
- components: { BaseFieldI18n, ShowValue },
17
+ components: { BaseForm, BaseFieldI18n, ShowValue },
28
18
  setup() {
29
- const value = ref(null);
30
- return { args, value };
19
+ const form = ref({
20
+ _status: 422,
21
+ name: {
22
+ en: 'Name',
23
+ fr: 'Nom',
24
+ },
25
+ });
26
+ return { args, form };
31
27
  },
32
28
  template: `
33
- <BaseFieldI18n v-model="value" v-bind="args" class="w-full"></BaseFieldI18n>
34
- <ShowValue :value="value" />
29
+ <BaseForm method="post" url="https://faker.witify.io/api/todos" :data="form">
30
+ <BaseFieldI18n v-model="form.name" v-bind="args" class="w-full"></BaseFieldI18n>
31
+ <button type="submit" class="btn btn-primary mt-5">Submit</button>
32
+ <ShowValue :value="form" />
33
+ </BaseForm>
35
34
  `,
36
35
  });
37
36
 
@@ -19,6 +19,7 @@ const Template = (args) => ({
19
19
  },
20
20
  setup() {
21
21
  const form = ref({
22
+ _status: 422,
22
23
  number: 1.1,
23
24
  });
24
25
  return { args, form };
@@ -26,7 +27,7 @@ const Template = (args) => ({
26
27
  template: `
27
28
  <BaseForm
28
29
  method="post"
29
- url="https://faker.witify.io/api/todos/422"
30
+ url="https://faker.witify.io/api/todos"
30
31
  :data="form"
31
32
  v-slot="{disabled}"
32
33
  >
@@ -216,7 +216,7 @@ function query() {
216
216
  .then((response: AxiosResponse) => {
217
217
  loading.value = false;
218
218
 
219
- errors.value = {};
219
+ clearErrors();
220
220
 
221
221
  successHandler(response);
222
222
 
@@ -295,10 +295,14 @@ function getErrorMessageByName(name: string): string | null {
295
295
  }
296
296
 
297
297
  function clearErrors(name = null): void {
298
- if (name == null) {
298
+ if (name == null || name == undefined) {
299
299
  errors.value = {};
300
- } else {
300
+ return;
301
+ }
302
+
303
+ if (errors.value[name]) {
301
304
  delete errors.value[name];
305
+ return;
302
306
  }
303
307
  }
304
308
 
@@ -3,6 +3,7 @@ import ShowValue from '@/../.storybook/components/ShowValue.vue';
3
3
  import { createFieldStory, options } from '../../.storybook/utils';
4
4
  import BaseAppNotifications from './BaseAppNotifications.vue';
5
5
  import QueryString from 'qs';
6
+ import { random } from 'lodash';
6
7
 
7
8
  const sizes = ['xs', 'sm', 'md'];
8
9
 
@@ -16,12 +17,12 @@ export default {
16
17
  },
17
18
  },
18
19
  args: {
19
- url: 'https://effettandem.com/api/content/articles',
20
- field: 'title',
20
+ url: 'https://faker.witify.io/api/todos',
21
+ field: 'name',
21
22
  primaryKey: 'id',
22
23
  showRouteUrl: (ids) => {
23
24
  const params = QueryString.stringify({ filter: { id: ids } });
24
- return `https://effettandem.com/api/content/articles?${params}`;
25
+ return `https://faker.witify.io/api/todos?${params}`;
25
26
  }
26
27
  },
27
28
  decorators: [() => ({ template: '<div class="mb-36"><story/></div>' })],
@@ -32,8 +33,8 @@ const Template = (args) => {
32
33
  components: { BaseHasMany, ShowValue, BaseAppNotifications },
33
34
  setup() {
34
35
  const value = ref([
35
- "9acd34f6-ecf3-43e3-b84e-d8fcf5382bf4",
36
- "994ecd49-0796-402d-ab1a-63d4e5c2a68a",
36
+ "4",
37
+ "6",
37
38
  ]);
38
39
  return { args, value };
39
40
  },
@@ -81,7 +82,7 @@ ShowRouteUrl.args = {
81
82
  max: 3,
82
83
  showRouteUrl: (ids) => {
83
84
  const params = QueryString.stringify({ filter: { id: ids } });
84
- return `https://effettandem.com/api/content/articles?${params}`;
85
+ return `https://faker.witify.io/api/todos?${params}`;
85
86
  },
86
87
  };
87
88
 
@@ -221,6 +222,53 @@ export const SlotEmpty = (args) => {
221
222
  };
222
223
  };
223
224
 
225
+ const RaceConditionTemplate = (args) => {
226
+ return {
227
+ components: { BaseHasMany, ShowValue, BaseAppNotifications },
228
+ setup() {
229
+
230
+ const value = ref([
231
+ "4",
232
+ "6",
233
+ ]);
234
+
235
+ const valueExternal = ref([]);
236
+
237
+ const intervalId = setInterval(() => {
238
+ const newValue = [random(1, 10)];
239
+ value.value = newValue;
240
+ valueExternal.value = newValue;
241
+ }, 300);
242
+
243
+ setTimeout(() => {
244
+ clearInterval(intervalId);
245
+ }, 1000);
246
+
247
+ return { args, value, valueExternal };
248
+ },
249
+ template: `
250
+ <BaseHasMany
251
+ v-model="value"
252
+ v-bind="args"
253
+ ></BaseHasMany>
254
+
255
+ <br>
256
+
257
+ <p class="-mb-4">Value from data</p>
258
+ <ShowValue :value="valueExternal" />
259
+
260
+ <br>
261
+
262
+ <p class="-mb-4">Value from component</p>
263
+ <ShowValue :value="value" />
264
+ <BaseAppNotifications />
265
+ `,
266
+ };
267
+ };
268
+
269
+ export const RaceCondition = RaceConditionTemplate.bind({});
270
+ RaceCondition.args = {};
271
+
224
272
  export const Field = createFieldStory({
225
273
  component: BaseHasMany,
226
274
  componentName: 'BaseHasMany',
@@ -50,6 +50,7 @@ import { config } from '@/index';
50
50
  import { PropType } from 'vue';
51
51
  import BaseTagAutocompleteFetch from './BaseTagAutocompleteFetch.vue';
52
52
  import { AxiosResponse } from 'axios';
53
+ import { getItems } from '@/utils/getApiData';
53
54
 
54
55
  const props = defineProps({
55
56
  modelValue: {
@@ -128,51 +129,44 @@ watch(
128
129
 
129
130
  watch(
130
131
  () => props.modelValue,
131
- debounce(() => fetchModels(), 300),
132
+ debounce(() => fetchModels(), 100),
132
133
  { immediate: true }
133
134
  );
134
135
 
135
136
  function fetchModels() {
136
137
 
137
- console.log('check if currentModels is undefined');
138
-
139
138
  if (props.currentModels !== undefined) {
140
139
  return;
141
140
  }
142
141
 
143
- console.log('check if showRouteUrl is empty');
144
-
145
- if (props.showRouteUrl == null) {
142
+ if (props.showRouteUrl == undefined) {
146
143
  return;
147
144
  }
148
145
 
149
- console.log('check if modelValue is empty');
150
-
151
146
  if (!props.modelValue) {
152
147
  models.value = [];
153
148
  return;
154
149
  }
155
150
 
156
- console.log('check models');
157
-
158
-
159
151
  // Do not fetch if the modelValue is the same as the local models
160
152
 
153
+ // Get primaryKeys as string for comparison
161
154
  const ids = props.modelValue.map((id: number | string) => id.toString());
162
-
163
- const localModelIds = models.value.map((m) => m[props.primaryKey]);
155
+ const localModelIds = models.value.map((m) => '' + m[props.primaryKey]);
164
156
 
165
157
  if (isEqual(ids, localModelIds)) {
166
158
  return;
167
159
  }
168
160
 
169
- console.log('fetch models');
170
-
171
161
  http
172
162
  .get(props.showRouteUrl(ids))
173
163
  .then((response: AxiosResponse) => {
174
- models.value = response.data.data.filter((i: any) => {
175
- return ids.includes(i[props.primaryKey]);
164
+
165
+ const items = getItems(response.data);
166
+
167
+ models.value = items.filter((i: Record<string, any>) => {
168
+ // convert primary keys to string for comparison
169
+ return ids.includes('' + i[props.primaryKey]);
176
170
  });
177
171
  })
178
172
  .catch((e: Error) => e);
@@ -367,6 +367,10 @@ function blurAction() {
367
367
  input.value?.blur();
368
368
  }
369
369
 
370
+ function selectAction() {
371
+ input.value?.select();
372
+ }
373
+
370
374
  function onIconLeftClickInternal() {
371
375
  if (props.onIconLeftClick) {
372
376
  props.onIconLeftClick();
@@ -386,5 +390,6 @@ function onIconRightClickInternal() {
386
390
  defineExpose({
387
391
  focus: focusAction,
388
392
  blur: blurAction,
393
+ select: selectAction,
389
394
  });
390
395
  </script>
@@ -30,9 +30,10 @@ export function useField(config: Config) {
30
30
  const fieldName = inject('field:name', ref('')) as Ref<string>;
31
31
  const fieldSize = inject('field:size', ref('md')) as Ref<Size>;
32
32
 
33
- const fieldOnUpdate = inject('field:onUpdate', () => {
33
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
34
+ const fieldOnUpdate = inject('field:onUpdate', (name?: string | null | undefined) => {
34
35
  return;
35
- }) as () => void;
36
+ }) as (name?: string | null | undefined) => void;
36
37
 
37
38
  const getErrorMessageByName = inject(
38
39
  'form:getErrorMessageByName',
@@ -121,7 +122,7 @@ export function useField(config: Config) {
121
122
 
122
123
  function emitUpdate(value: any) {
123
124
  emit('update:modelValue', value);
124
- fieldOnUpdate();
125
+ fieldOnUpdate(nameInternal.value);
125
126
  }
126
127
 
127
128
  return {
@@ -6,18 +6,13 @@ import {
6
6
  PaginationMetadata,
7
7
  } from '@/types';
8
8
  import { isArray } from 'lodash';
9
+ import { getItems } from '@/utils/getApiData';
9
10
 
10
11
  export function useHasPaginatedData(
11
12
  data: Ref<Collection | PaginatedCollection | ResourceCollection | null>
12
13
  ) {
13
14
  const items = computed(() => {
14
- if (!data.value) {
15
- return [];
16
- }
17
- if (isArray(data.value)) {
18
- return data.value;
19
- }
20
- return data.value.data;
15
+ return getItems(data.value);
21
16
  });
22
17
 
23
18
  const paginationMetadata = computed<PaginationMetadata | null>(() => {
@@ -0,0 +1,12 @@
1
+ import { Collection, PaginatedCollection, ResourceCollection } from "@/types";
2
+ import { isArray } from "lodash";
3
+
4
+ export function getItems(data: Collection | PaginatedCollection | ResourceCollection | null) {
5
+ if (!data) {
6
+ return [];
7
+ }
8
+ if (isArray(data)) {
9
+ return data;
10
+ }
11
+ return data.data;
12
+ }