sprintify-ui 0.6.73 → 0.6.75

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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprintify-ui",
3
- "version": "0.6.73",
3
+ "version": "0.6.75",
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: 'id',
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
  };
@@ -24,7 +28,7 @@ export default {
24
28
  const Template = (args) => ({
25
29
  components: { BaseBelongsToFetch, ShowValue },
26
30
  setup() {
27
- const value = ref(null);
31
+ const value = ref(4);
28
32
  return { args, value };
29
33
  },
30
34
  template: `
@@ -74,9 +78,6 @@ export const Sizes = (args) => ({
74
78
 
75
79
  export const Disabled = Template.bind({});
76
80
  Disabled.args = {
77
- currentModel: options[0],
78
- primaryKey: 'value',
79
- field: 'label',
80
81
  disabled: true,
81
82
  };
82
83
 
@@ -103,7 +104,7 @@ export const SlotOption = (args) => {
103
104
  }"
104
105
  >
105
106
  <p class="text-sm font-medium">{{ option.title }}</p>
106
- <p class="opacity-60 text-xs">{{ option.owner?.name }}</p>
107
+ <p class="opacity-60 text-xs">{{ option.description }}</p>
107
108
  </div>
108
109
  </template>
109
110
  </BaseBelongsToFetch>
@@ -165,14 +166,15 @@ export const WithSelect = (args) => {
165
166
  return {
166
167
  components: { BaseBelongsToFetch, ShowValue },
167
168
  setup() {
168
- const value = ref(options[0]);
169
169
  const selected = ref(null);
170
+ const value = ref(5);
170
171
 
171
172
  const select = {
172
173
  options: [
173
174
  { label: 'All', value: 'all' },
174
- { label: 'Video', value: 'video' },
175
- { label: 'Article', value: 'article' },
175
+ { label: 'Personal', value: 'personal' },
176
+ { label: 'Work', value: 'work' },
177
+ { label: 'Family', value: 'family' },
176
178
  ],
177
179
  labelKey: 'label',
178
180
  valueKey: 'value',
@@ -183,10 +185,10 @@ export const WithSelect = (args) => {
183
185
 
184
186
  const url = computed(() => {
185
187
  if (selected.value == 'all' || !selected.value) {
186
- return 'https://effettandem.com/api/content/articles';
188
+ return 'https://faker.witify.io/api/todos';
187
189
  }
188
190
  return (
189
- 'https://effettandem.com/api/content/articles' +
191
+ 'https://faker.witify.io/api/todos' +
190
192
  '?type=' +
191
193
  selected.value
192
194
  );
@@ -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 { debounce, 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,
@@ -144,39 +145,18 @@ const autocompleteFetch = ref<InstanceType<
144
145
  > | null>(null);
145
146
 
146
147
  const model = ref(props.currentModel);
148
+ const ensureModelIsFilledDebounced = debounce(ensureModelIsFilled, 100);
147
149
 
148
150
  watch(
149
151
  () => props.currentModel,
150
- (newValue, oldValue) => {
151
- model.value = newValue;
152
- },
153
- { deep: true }
152
+ ensureModelIsFilledDebounced,
153
+ { immediate: true },
154
154
  );
155
155
 
156
156
  watch(
157
157
  () => props.modelValue,
158
- (newValue, oldValue) => {
159
- if (!props.modelValue) {
160
- model.value = null;
161
- return;
162
- }
163
-
164
- if (newValue == oldValue) {
165
- return;
166
- }
167
-
168
- if (props.showRouteUrl == null) {
169
- return;
170
- }
171
-
172
- http
173
- .get(props.showRouteUrl(props.modelValue))
174
- .then((response: AxiosResponse) => {
175
- model.value = response.data.data;
176
- })
177
- .catch((e: Error) => e);
178
- },
179
- { immediate: true }
158
+ ensureModelIsFilledDebounced,
159
+ { immediate: true },
180
160
  );
181
161
 
182
162
  function onUpdate(newModel: Option | null) {
@@ -189,6 +169,54 @@ function onUpdate(newModel: Option | null) {
189
169
  }
190
170
  }
191
171
 
172
+ function ensureModelIsFilled() {
173
+
174
+ if (props.modelValue == null) {
175
+ model.value = null;
176
+ return;
177
+ }
178
+
179
+ const modelId = model.value ? model.value[props.primaryKey as never] : null;
180
+
181
+ // Current value is already set
182
+ if (props.modelValue == modelId) {
183
+ return;
184
+ }
185
+
186
+ // Try with current model
187
+ if (props.currentModel && props.currentModel[props.primaryKey as never] == props.modelValue) {
188
+ model.value = props.currentModel;
189
+ return;
190
+ }
191
+
192
+ // Try with show route
193
+
194
+ if (props.showRouteUrl == null) {
195
+ return;
196
+ }
197
+
198
+ http
199
+ .get(props.showRouteUrl(props.modelValue))
200
+ .then((response: AxiosResponse) => {
201
+
202
+ const data = response.data as Record<string, unknown>;
203
+
204
+ if (!isObject(data)) {
205
+ return;
206
+ }
207
+
208
+ if (data[props.primaryKey as never] == props.modelValue) {
209
+ model.value = data;
210
+ return;
211
+ }
212
+
213
+ if (isObject(data.data) && data.data[props.primaryKey as never] == props.modelValue) {
214
+ model.value = data.data;
215
+ return;
216
+ }
217
+ });
218
+ }
219
+
192
220
  defineExpose({
193
221
  focus: () => autocompleteFetch.value?.focus(),
194
222
  blur: () => autocompleteFetch.value?.blur(),
@@ -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',
@@ -129,7 +129,7 @@ watch(
129
129
 
130
130
  watch(
131
131
  () => props.modelValue,
132
- debounce(() => fetchModels(), 200),
132
+ debounce(() => fetchModels(), 100),
133
133
  { immediate: true }
134
134
  );
135
135
 
@@ -150,9 +150,9 @@ function fetchModels() {
150
150
 
151
151
  // Do not fetch if the modelValue is the same as the local models
152
152
 
153
+ // Get primaryKeys as string for comparison
153
154
  const ids = props.modelValue.map((id: number | string) => id.toString());
154
-
155
- const localModelIds = models.value.map((m) => m[props.primaryKey]);
155
+ const localModelIds = models.value.map((m) => '' + m[props.primaryKey]);
156
156
 
157
157
  if (isEqual(ids, localModelIds)) {
158
158
  return;
@@ -164,8 +164,9 @@ function fetchModels() {
164
164
 
165
165
  const items = getItems(response.data);
166
166
 
167
- models.value = items.filter((i: any) => {
168
- return ids.includes(i[props.primaryKey]);
167
+ models.value = items.filter((i: Record<string, any>) => {
168
+ // convert primary keys to string for comparison
169
+ return ids.includes('' + i[props.primaryKey]);
169
170
  });
170
171
  })
171
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 {