@fastkit/vui 0.7.50 → 0.7.55

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.
@@ -1,5 +1,5 @@
1
1
  import './VSelect.scss';
2
- import { ref, VNodeChild, defineComponent } from 'vue';
2
+ import { ref, VNodeChild, defineComponent, PropType, computed } from 'vue';
3
3
  import {
4
4
  createFormSelectorSettings,
5
5
  useFormSelectorControl,
@@ -8,6 +8,8 @@ import {
8
8
  FormControlSlots,
9
9
  FormSelectorItemControl,
10
10
  createPropsOptions,
11
+ VNodeChildOrSlot,
12
+ resolveVNodeChildOrSlots,
11
13
  } from '@fastkit/vue-kit';
12
14
  import { VFormControl } from '../VFormControl';
13
15
  import {
@@ -40,6 +42,8 @@ export const VSelect = defineComponent({
40
42
  ...defineSlotsProps<FormControlSlots & InputBoxSlots>(),
41
43
  ...createPropsOptions({
42
44
  placeholder: String,
45
+ loadingMessage: {} as PropType<VNodeChildOrSlot>,
46
+ failedToLoadItemsMessage: {} as PropType<VNodeChildOrSlot>,
43
47
  }),
44
48
  },
45
49
  emits,
@@ -62,6 +66,22 @@ export const VSelect = defineComponent({
62
66
  const vui = useVui();
63
67
  const iconName = vui.icon('menuDown');
64
68
  const fieldRef = ref<HTMLElement | null>(null);
69
+ const loadingMessageRef = computed(() => {
70
+ const slot = resolveVNodeChildOrSlots(
71
+ props.loadingMessage,
72
+ vui.setting('loadingMessage'),
73
+ 'Loading...',
74
+ );
75
+ return slot && slot(vui);
76
+ });
77
+ const failedToLoadItemsMessageRef = computed(() => {
78
+ const slot = resolveVNodeChildOrSlots(
79
+ props.failedToLoadItemsMessage,
80
+ vui.setting('failedToLoadDataMessage'),
81
+ 'Failed to load data.',
82
+ );
83
+ return slot && slot(vui);
84
+ });
65
85
 
66
86
  const focus = (opts?: FocusOptions): void => {
67
87
  fieldRef.value && fieldRef.value.focus(opts);
@@ -69,7 +89,17 @@ export const VSelect = defineComponent({
69
89
 
70
90
  const renderSelections = (selectedItems: FormSelectorItemControl[]) => {
71
91
  const children: VNodeChild[] = [];
72
- if (selectedItems.length) {
92
+ if (inputControl.itemsLoadFailed) {
93
+ children.push(
94
+ <span class="v-select__placeholder">
95
+ {failedToLoadItemsMessageRef.value}
96
+ </span>,
97
+ );
98
+ } else if (inputControl.itemsLoading) {
99
+ children.push(
100
+ <span class="v-select__placeholder">{loadingMessageRef.value}</span>,
101
+ );
102
+ } else if (selectedItems.length) {
73
103
  selectedItems.forEach((item, index) => {
74
104
  if (index > 0) {
75
105
  children.push(vui.selectionSeparator());
@@ -136,9 +166,12 @@ export const VSelect = defineComponent({
136
166
  <VControlField
137
167
  class="v-select__input"
138
168
  ref={this.fieldRef()}
169
+ loading={selectorControl.itemsLoading}
170
+ error={selectorControl.itemsLoadFailed}
139
171
  startAdornment={this.startAdornment}
140
172
  endAdornment={this.endAdornment}
141
173
  // tabindex={this.computedTabindex}
174
+ size={this.size}
142
175
  focused={this.menuOpened}
143
176
  onClick={(ev) => {
144
177
  if (this.canOperation && !control.isActive) {
@@ -185,24 +218,43 @@ export const VSelect = defineComponent({
185
218
  </div>
186
219
  </div>,
187
220
  ],
188
- endAdornment: () =>
189
- (this.clearable && this.nodeControl.value != null && (
190
- <VButton
191
- key="clear"
192
- icon={this.$vui.icon('clear')}
193
- rounded
194
- onClick={(ev) => {
195
- ev.stopPropagation();
196
- this.nodeControl.clear();
197
- }}
198
- />
199
- )) || (
221
+ endAdornment: () => {
222
+ if (selectorControl.itemsLoadFailed) {
223
+ return (
224
+ <VButton
225
+ key="reload"
226
+ icon={this.$vui.icon('reload')}
227
+ rounded
228
+ onClick={(ev) => {
229
+ ev.stopPropagation();
230
+ selectorControl.loadItems();
231
+ }}
232
+ />
233
+ );
234
+ }
235
+
236
+ if (this.clearable && this.nodeControl.value != null) {
237
+ return (
238
+ <VButton
239
+ key="clear"
240
+ icon={this.$vui.icon('clear')}
241
+ rounded
242
+ onClick={(ev) => {
243
+ ev.stopPropagation();
244
+ this.nodeControl.clear();
245
+ }}
246
+ />
247
+ );
248
+ }
249
+
250
+ return (
200
251
  <VIcon
201
252
  key="icon"
202
253
  name={this.iconName}
203
254
  rotate={this.menuOpened ? 180 : 0}
204
255
  />
205
- ),
256
+ );
257
+ },
206
258
  }}
207
259
  />,
208
260
  ],
@@ -6,6 +6,7 @@ import {
6
6
  createFormControlProps,
7
7
  FormControlSlots,
8
8
  defineSlotsProps,
9
+ TextInputEmits,
9
10
  } from '@fastkit/vue-kit';
10
11
  import { VFormControl } from '../VFormControl';
11
12
  import {
@@ -36,6 +37,10 @@ function createTextFieldProps() {
36
37
  };
37
38
  }
38
39
 
40
+ export type TextFieldPropsOptions = ReturnType<typeof createTextFieldProps>;
41
+
42
+ export type TextFieldEmits = TextInputEmits;
43
+
39
44
  export type TextFieldInput = ExtractPropInput<
40
45
  ReturnType<typeof createTextFieldProps>
41
46
  >;
@@ -77,6 +82,7 @@ export const VTextField = defineComponent({
77
82
  class="v-text-field__input"
78
83
  startAdornment={this.startAdornment}
79
84
  endAdornment={this.endAdornment}
85
+ size={this.size}
80
86
  v-slots={{
81
87
  ...this.$slots,
82
88
  default: () =>
@@ -68,6 +68,7 @@ export const VTextarea = defineComponent({
68
68
  default: () => (
69
69
  <VControlField
70
70
  class="v-textarea__input"
71
+ size={this.size}
71
72
  autoHeight
72
73
  startAdornment={this.startAdornment}
73
74
  endAdornment={this.endAdornment}
@@ -120,7 +120,6 @@ export const VWysiwygEditor = defineComponent({
120
120
  onUpdate: (ev) => {
121
121
  inputControl.value = ev.editor.getHTML();
122
122
  textRef.value = ev.editor.getText();
123
- console.log(textRef.value);
124
123
  },
125
124
  autofocus: props.autofocus,
126
125
  content: props.modelValue,
@@ -256,6 +255,7 @@ export const VWysiwygEditor = defineComponent({
256
255
  autoHeight
257
256
  startAdornment={this.startAdornment}
258
257
  endAdornment={this.endAdornment}
258
+ size={this.size}
259
259
  // onClickHost={(ev) => {
260
260
  // this.focus();
261
261
  // }}
@@ -26,6 +26,7 @@ export * from './VOption';
26
26
  export * from './VOptionGroup';
27
27
  export * from './VSelect';
28
28
  export * from './VTextField';
29
+ export * from './VNumberField';
29
30
  export * from './VTextarea';
30
31
  export * from './VWysiwygEditor';
31
32
  export * from './VForm';
@@ -27,6 +27,7 @@ export function createControlProps() {
27
27
  return CONTROL_SIZES.includes(value);
28
28
  },
29
29
  },
30
+ loading: Boolean,
30
31
  });
31
32
  }
32
33
 
@@ -3,6 +3,19 @@
3
3
  display: block;
4
4
  }
5
5
 
6
+ &__placeholder {
7
+ display: flex;
8
+ align-items: center;
9
+ margin-top: 8px;
10
+ font-style: italic;
11
+ opacity: 0.5;
12
+
13
+ &--error {
14
+ color: var(--main-color);
15
+ opacity: 1;
16
+ }
17
+ }
18
+
6
19
  &--stacked &__body {
7
20
  display: flex;
8
21
  flex-direction: column;
@@ -1,5 +1,5 @@
1
1
  import './form-selector.scss';
2
- import { VNodeChild, defineComponent } from 'vue';
2
+ import { VNodeChild, defineComponent, PropType, computed } from 'vue';
3
3
  import {
4
4
  defineSlotsProps,
5
5
  TypedSlot,
@@ -11,9 +11,14 @@ import {
11
11
  ResolvedFormSelectorItemData,
12
12
  FormNodeControl,
13
13
  renderSlotOrEmpty,
14
+ VNodeChildOrSlot,
15
+ resolveVNodeChildOrSlots,
14
16
  } from '@fastkit/vue-kit';
15
17
  import { createControlProps, useControl } from './control';
16
18
  import { VFormControl } from '../components/VFormControl';
19
+ import { useVui } from '../injections';
20
+ import { VProgressCircular } from '../components/loading';
21
+ import { CONTROL_LOADING_SPINNER_SIZES } from '../schemes';
17
22
 
18
23
  export interface DefineFormSelectorComponentOptions {
19
24
  name: string;
@@ -51,6 +56,10 @@ export function defineFormSelectorComponent(
51
56
  type: Boolean,
52
57
  default: true,
53
58
  },
59
+ // eslint-disable-next-line vue/require-prop-types
60
+ loadingMessage: {} as PropType<VNodeChildOrSlot>,
61
+ // eslint-disable-next-line vue/require-prop-types
62
+ failedToLoadItemsMessage: {} as PropType<VNodeChildOrSlot>,
54
63
  ...defineSlotsProps<FormControlSlots>(),
55
64
  },
56
65
  emits,
@@ -60,9 +69,28 @@ export function defineFormSelectorComponent(
60
69
  defaultMultiple,
61
70
  });
62
71
  const control = useControl(props);
72
+ const vui = useVui();
73
+ const loadingMessageRef = computed(() => {
74
+ const slot = resolveVNodeChildOrSlots(
75
+ props.loadingMessage,
76
+ vui.setting('loadingMessage'),
77
+ 'Loading...',
78
+ );
79
+ return slot && slot(vui);
80
+ });
81
+ const failedToLoadItemsMessageRef = computed(() => {
82
+ const slot = resolveVNodeChildOrSlots(
83
+ props.failedToLoadItemsMessage,
84
+ vui.setting('failedToLoadDataMessage'),
85
+ 'Failed to load data.',
86
+ );
87
+ return slot && slot(vui);
88
+ });
63
89
  return {
64
90
  ...selectorControl.expose(),
65
91
  ...control,
92
+ loadingMessageRef,
93
+ failedToLoadItemsMessageRef,
66
94
  };
67
95
  },
68
96
  render() {
@@ -84,10 +112,29 @@ export function defineFormSelectorComponent(
84
112
  hint={this.hint}
85
113
  hinttip={this.hinttip}
86
114
  requiredChip={this.requiredChip}
115
+ error={selectorControl.itemsLoadFailed}
87
116
  v-slots={{
88
117
  ...this.$slots,
89
118
  default: () => (
90
119
  <div class="v-form-selector__body">
120
+ {selectorControl.itemsLoadFailed && (
121
+ <div
122
+ key="failed"
123
+ class="v-form-selector__placeholder v-form-selector__placeholder--error">
124
+ {this.failedToLoadItemsMessageRef}
125
+ </div>
126
+ )}
127
+ {selectorControl.itemsLoading && (
128
+ <div key="loading" class="v-form-selector__placeholder">
129
+ <VProgressCircular
130
+ indeterminate
131
+ class="mr-1"
132
+ size={CONTROL_LOADING_SPINNER_SIZES[this.size || 'md']}
133
+ />
134
+
135
+ {this.loadingMessageRef}
136
+ </div>
137
+ )}
91
138
  {this.propItems.map((attrs) => {
92
139
  const selected = selectorControl.isSelected(attrs.value);
93
140
  return itemRenderer({
@@ -5,3 +5,9 @@ export type ControlSize = typeof CONTROL_SIZES[number];
5
5
  export const CONTROL_FIELD_VARIANTS = ['outlined', 'filled', 'flat'] as const;
6
6
 
7
7
  export type ControlFieldVariant = typeof CONTROL_FIELD_VARIANTS[number];
8
+
9
+ export const CONTROL_LOADING_SPINNER_SIZES: Record<ControlSize, number> = {
10
+ sm: 16,
11
+ md: 20,
12
+ lg: 24,
13
+ };
package/src/service.tsx CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  type VueStackService,
9
9
  type VDialogProps,
10
10
  type VStackControl,
11
+ type VNodeChildOrSlot,
11
12
  resolveVStackDynamicInput,
12
13
  } from '@fastkit/vue-kit';
13
14
  import type { Router } from 'vue-router';
@@ -60,8 +61,10 @@ export interface VuiServiceUISettings {
60
61
  variant?: ColorVariant;
61
62
  };
62
63
  hinttipDelay?: FormControlHinttipDelay;
63
- noDataMessage?: VNodeChild | (() => VNodeChild);
64
- noResultsMessage?: VNodeChild | (() => VNodeChild);
64
+ noDataMessage?: VNodeChildOrSlot;
65
+ noResultsMessage?: VNodeChildOrSlot;
66
+ loadingMessage?: VNodeChildOrSlot;
67
+ failedToLoadDataMessage?: VNodeChildOrSlot;
65
68
  }
66
69
 
67
70
  export type RawVuiServiceUISettings = Partial<VuiServiceUISettings>;
@@ -102,6 +105,7 @@ export interface VuiServiceIconSettings {
102
105
  editorRedo: IconName;
103
106
  hinttip: IconName;
104
107
  clear: IconName;
108
+ reload: IconName;
105
109
  }
106
110
 
107
111
  export type RawVuiServiceIconSettings = Partial<VuiServiceIconSettings>;
@@ -173,6 +173,7 @@ export function viteVuiPlugin(
173
173
  editorRedo: 'mdi-redo-variant' as any,
174
174
  hinttip: 'mdi-help-circle-outline' as any,
175
175
  clear: 'mdi-close' as any,
176
+ reload: 'mdi-reload' as any,
176
177
  // navigationExpand: (gen, active) => {
177
178
  // return gen({
178
179
  // name: 'mdi-menu-down' as any,