sprintify-ui 0.6.74 → 0.6.76
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.
- package/dist/sprintify-ui.es.js +4618 -4606
- package/package.json +1 -1
- package/src/components/BaseBelongsToFetch.stories.js +4 -7
- package/src/components/BaseBelongsToFetch.vue +55 -48
- package/src/components/BaseHasMany.stories.js +8 -4
- package/src/components/BaseHasMany.vue +56 -32
- package/src/components/BaseTagAutocomplete.vue +5 -2
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ export default {
|
|
|
17
17
|
args: {
|
|
18
18
|
url: 'https://faker.witify.io/api/todos',
|
|
19
19
|
field: 'name',
|
|
20
|
-
primaryKey: '
|
|
20
|
+
primaryKey: 'id',
|
|
21
21
|
showRouteUrl(id) {
|
|
22
22
|
return `https://faker.witify.io/api/todos/${id}`;
|
|
23
23
|
},
|
|
@@ -28,7 +28,7 @@ export default {
|
|
|
28
28
|
const Template = (args) => ({
|
|
29
29
|
components: { BaseBelongsToFetch, ShowValue },
|
|
30
30
|
setup() {
|
|
31
|
-
const value = ref(
|
|
31
|
+
const value = ref(4);
|
|
32
32
|
return { args, value };
|
|
33
33
|
},
|
|
34
34
|
template: `
|
|
@@ -78,9 +78,6 @@ export const Sizes = (args) => ({
|
|
|
78
78
|
|
|
79
79
|
export const Disabled = Template.bind({});
|
|
80
80
|
Disabled.args = {
|
|
81
|
-
currentModel: options[0],
|
|
82
|
-
primaryKey: 'value',
|
|
83
|
-
field: 'label',
|
|
84
81
|
disabled: true,
|
|
85
82
|
};
|
|
86
83
|
|
|
@@ -107,7 +104,7 @@ export const SlotOption = (args) => {
|
|
|
107
104
|
}"
|
|
108
105
|
>
|
|
109
106
|
<p class="text-sm font-medium">{{ option.title }}</p>
|
|
110
|
-
<p class="opacity-60 text-xs">{{ option.
|
|
107
|
+
<p class="opacity-60 text-xs">{{ option.description }}</p>
|
|
111
108
|
</div>
|
|
112
109
|
</template>
|
|
113
110
|
</BaseBelongsToFetch>
|
|
@@ -169,8 +166,8 @@ export const WithSelect = (args) => {
|
|
|
169
166
|
return {
|
|
170
167
|
components: { BaseBelongsToFetch, ShowValue },
|
|
171
168
|
setup() {
|
|
172
|
-
const value = ref(options[0]);
|
|
173
169
|
const selected = ref(null);
|
|
170
|
+
const value = ref(5);
|
|
174
171
|
|
|
175
172
|
const select = {
|
|
176
173
|
options: [
|
|
@@ -49,7 +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
|
+
import { debounce, isObject } from 'lodash';
|
|
53
53
|
|
|
54
54
|
const props = defineProps({
|
|
55
55
|
modelValue: {
|
|
@@ -144,62 +144,21 @@ const autocompleteFetch = ref<InstanceType<
|
|
|
144
144
|
typeof BaseAutocompleteFetch
|
|
145
145
|
> | null>(null);
|
|
146
146
|
|
|
147
|
-
const model = ref(
|
|
147
|
+
const model = ref<Option | null>(null);
|
|
148
|
+
const ensureModelIsFilledDebounced = debounce(ensureModelIsFilled, 100);
|
|
148
149
|
|
|
149
150
|
watch(
|
|
150
151
|
() => props.currentModel,
|
|
151
|
-
|
|
152
|
-
model.value = newValue;
|
|
153
|
-
},
|
|
154
|
-
{ deep: true }
|
|
152
|
+
ensureModelIsFilledDebounced,
|
|
155
153
|
);
|
|
156
154
|
|
|
157
155
|
watch(
|
|
158
156
|
() => props.modelValue,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (props.showRouteUrl == null) {
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (props.currentModel !== undefined) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (!props.modelValue) {
|
|
170
|
-
model.value = null;
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (newValue == oldValue) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
http
|
|
179
|
-
.get(props.showRouteUrl(props.modelValue))
|
|
180
|
-
.then((response: AxiosResponse) => {
|
|
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
|
-
}
|
|
197
|
-
})
|
|
198
|
-
.catch((e: Error) => e);
|
|
199
|
-
},
|
|
200
|
-
{ immediate: true }
|
|
157
|
+
ensureModelIsFilledDebounced,
|
|
201
158
|
);
|
|
202
159
|
|
|
160
|
+
ensureModelIsFilledDebounced();
|
|
161
|
+
|
|
203
162
|
function onUpdate(newModel: Option | null) {
|
|
204
163
|
if (!newModel) {
|
|
205
164
|
model.value = null;
|
|
@@ -210,6 +169,54 @@ function onUpdate(newModel: Option | null) {
|
|
|
210
169
|
}
|
|
211
170
|
}
|
|
212
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
|
+
|
|
213
220
|
defineExpose({
|
|
214
221
|
focus: () => autocompleteFetch.value?.focus(),
|
|
215
222
|
blur: () => autocompleteFetch.value?.blur(),
|
|
@@ -36,12 +36,17 @@ const Template = (args) => {
|
|
|
36
36
|
"4",
|
|
37
37
|
"6",
|
|
38
38
|
]);
|
|
39
|
-
|
|
39
|
+
const currentModels = ref([
|
|
40
|
+
{ id: 4, name: 'Todo 4 (local)' },
|
|
41
|
+
{ id: 6, name: 'Todo 6 (local)' },
|
|
42
|
+
])
|
|
43
|
+
return { args, value, currentModels };
|
|
40
44
|
},
|
|
41
45
|
template: `
|
|
42
46
|
<BaseHasMany
|
|
43
47
|
v-model="value"
|
|
44
48
|
v-bind="args"
|
|
49
|
+
:current-models="currentModels"
|
|
45
50
|
></BaseHasMany>
|
|
46
51
|
<ShowValue :value="value" />
|
|
47
52
|
<BaseAppNotifications />
|
|
@@ -56,8 +61,9 @@ export const Disabled = (args) => {
|
|
|
56
61
|
return {
|
|
57
62
|
components: { BaseHasMany, ShowValue },
|
|
58
63
|
setup() {
|
|
64
|
+
// current model is incorrect, to test component's resilience
|
|
59
65
|
const currentModel = options[1];
|
|
60
|
-
const value = ref([
|
|
66
|
+
const value = ref([7]);
|
|
61
67
|
return { args, value, currentModel };
|
|
62
68
|
},
|
|
63
69
|
template: `<BaseHasMany
|
|
@@ -65,8 +71,6 @@ export const Disabled = (args) => {
|
|
|
65
71
|
v-model="value"
|
|
66
72
|
:current-models="[currentModel]"
|
|
67
73
|
:disabled="true"
|
|
68
|
-
primaryKey="value"
|
|
69
|
-
field="label"
|
|
70
74
|
></BaseHasMany>
|
|
71
75
|
<ShowValue :value="value" />`,
|
|
72
76
|
};
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
</template>
|
|
45
45
|
|
|
46
46
|
<script lang="ts" setup>
|
|
47
|
-
import { debounce
|
|
47
|
+
import { debounce } from 'lodash';
|
|
48
48
|
import { Option } from '@/types';
|
|
49
49
|
import { config } from '@/index';
|
|
50
50
|
import { PropType } from 'vue';
|
|
@@ -113,74 +113,98 @@ const tagAutocompleteFetch = ref<InstanceType<
|
|
|
113
113
|
typeof BaseTagAutocompleteFetch
|
|
114
114
|
> | null>(null);
|
|
115
115
|
|
|
116
|
-
const models = ref(
|
|
116
|
+
const models = ref<Option[]>([]);
|
|
117
|
+
const ensureModelIsFilledDebounced = debounce(() => ensureModelsAreFilled(), 100);
|
|
117
118
|
|
|
118
119
|
watch(
|
|
119
120
|
() => props.currentModels,
|
|
120
|
-
|
|
121
|
-
if (isEqual(newValue, oldValue)) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
models.value = newValue ?? [];
|
|
126
|
-
},
|
|
121
|
+
ensureModelIsFilledDebounced,
|
|
127
122
|
{ deep: true }
|
|
128
123
|
);
|
|
129
124
|
|
|
130
125
|
watch(
|
|
131
126
|
() => props.modelValue,
|
|
132
|
-
|
|
133
|
-
{
|
|
127
|
+
ensureModelIsFilledDebounced,
|
|
128
|
+
{ deep: true }
|
|
134
129
|
);
|
|
135
130
|
|
|
136
|
-
|
|
131
|
+
ensureModelIsFilledDebounced();
|
|
137
132
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
function onUpdate(newModels: Option[]) {
|
|
134
|
+
models.value = newModels;
|
|
135
|
+
emit(
|
|
136
|
+
'update:modelValue',
|
|
137
|
+
newModels.map((m) => m[props.primaryKey]),
|
|
138
|
+
newModels,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
function ensureModelsAreFilled() {
|
|
143
|
+
|
|
144
|
+
if (!Array.isArray(props.modelValue)) {
|
|
145
|
+
models.value = [];
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
if (
|
|
149
|
+
if (props.modelValue.length == 0) {
|
|
147
150
|
models.value = [];
|
|
148
151
|
return;
|
|
149
152
|
}
|
|
150
153
|
|
|
151
|
-
//
|
|
154
|
+
// Remove incorrect models
|
|
152
155
|
|
|
153
|
-
// Get primaryKeys as string for comparison
|
|
154
156
|
const ids = props.modelValue.map((id: number | string) => id.toString());
|
|
155
|
-
const localModelIds = models.value.map((m) => '' + m[props.primaryKey]);
|
|
156
157
|
|
|
157
|
-
|
|
158
|
+
models.value = models.value.filter((m) => ids.includes(m[props.primaryKey] + ''));
|
|
159
|
+
|
|
160
|
+
const localModelIds = models.value.map((m) => m[props.primaryKey] + '');
|
|
161
|
+
|
|
162
|
+
let missingIds = ids.filter((id) => !localModelIds.includes(id));
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
// Current models are fully set
|
|
166
|
+
if (missingIds.length == 0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Try with current models
|
|
171
|
+
|
|
172
|
+
if (Array.isArray(props.currentModels)) {
|
|
173
|
+
missingIds.forEach((id) => {
|
|
174
|
+
const model = props.currentModels?.find((m) => m[props.primaryKey as never] == id);
|
|
175
|
+
|
|
176
|
+
if (model) {
|
|
177
|
+
models.value.push(model);
|
|
178
|
+
missingIds = missingIds.filter((i) => i != id);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Current models are fully set
|
|
184
|
+
if (missingIds.length == 0) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Try with show route
|
|
189
|
+
|
|
190
|
+
if (props.showRouteUrl == null) {
|
|
158
191
|
return;
|
|
159
192
|
}
|
|
160
193
|
|
|
161
194
|
http
|
|
162
|
-
.get(props.showRouteUrl(
|
|
195
|
+
.get(props.showRouteUrl(missingIds))
|
|
163
196
|
.then((response: AxiosResponse) => {
|
|
164
197
|
|
|
165
198
|
const items = getItems(response.data);
|
|
166
199
|
|
|
167
200
|
models.value = items.filter((i: Record<string, any>) => {
|
|
168
201
|
// convert primary keys to string for comparison
|
|
169
|
-
return ids.includes(
|
|
202
|
+
return ids.includes(i[props.primaryKey] + '');
|
|
170
203
|
});
|
|
171
204
|
})
|
|
172
205
|
.catch((e: Error) => e);
|
|
173
206
|
}
|
|
174
207
|
|
|
175
|
-
function onUpdate(newModels: Option[]) {
|
|
176
|
-
models.value = newModels;
|
|
177
|
-
emit(
|
|
178
|
-
'update:modelValue',
|
|
179
|
-
newModels.map((m) => m[props.primaryKey]),
|
|
180
|
-
newModels,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
208
|
defineExpose({
|
|
185
209
|
focus: () => tagAutocompleteFetch.value?.focus(),
|
|
186
210
|
blur: () => tagAutocompleteFetch.value?.blur(),
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
:key="selection.value ? selection.value : 'null'"
|
|
13
13
|
:class="selectionClass(selection)"
|
|
14
14
|
>
|
|
15
|
-
<div
|
|
15
|
+
<div
|
|
16
|
+
:title="selection.label"
|
|
17
|
+
class="truncate"
|
|
18
|
+
>
|
|
16
19
|
{{ selection.label }}
|
|
17
20
|
</div>
|
|
18
21
|
|
|
@@ -459,7 +462,7 @@ const inputClasses = computed(() => {
|
|
|
459
462
|
|
|
460
463
|
const selectionClass = (selection: NormalizedOption): string => {
|
|
461
464
|
|
|
462
|
-
const base = 'flex items-center rounded border';
|
|
465
|
+
const base = 'flex items-center rounded border overflow-hidden';
|
|
463
466
|
|
|
464
467
|
const fontSize = {
|
|
465
468
|
'xs': 'text-xs',
|