@nuxt/ui 2.9.0 → 2.10.0

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.
Files changed (71) hide show
  1. package/dist/module.d.mts +270 -2
  2. package/dist/module.d.ts +270 -2
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +21 -2
  5. package/dist/runtime/components/data/Table.vue +8 -6
  6. package/dist/runtime/components/data/Table.vue.d.ts +2 -1
  7. package/dist/runtime/components/elements/Accordion.vue +15 -13
  8. package/dist/runtime/components/elements/Accordion.vue.d.ts +5 -2
  9. package/dist/runtime/components/elements/Button.vue.d.ts +2 -2
  10. package/dist/runtime/components/elements/Dropdown.vue +31 -25
  11. package/dist/runtime/components/elements/Dropdown.vue.d.ts +2 -0
  12. package/dist/runtime/components/elements/Meter.vue +166 -0
  13. package/dist/runtime/components/elements/Meter.vue.d.ts +123 -0
  14. package/dist/runtime/components/elements/MeterGroup.d.ts +78 -0
  15. package/dist/runtime/components/elements/MeterGroup.mjs +172 -0
  16. package/dist/runtime/components/elements/Progress.vue +284 -0
  17. package/dist/runtime/components/elements/Progress.vue.d.ts +2 -0
  18. package/dist/runtime/components/forms/Checkbox.vue.d.ts +2 -2
  19. package/dist/runtime/components/forms/Form.vue +32 -7
  20. package/dist/runtime/components/forms/Form.vue.d.ts +2 -1
  21. package/dist/runtime/components/forms/Input.vue +5 -1
  22. package/dist/runtime/components/forms/Input.vue.d.ts +12 -3
  23. package/dist/runtime/components/forms/Radio.vue +10 -9
  24. package/dist/runtime/components/forms/Radio.vue.d.ts +3 -3
  25. package/dist/runtime/components/forms/RadioGroup.vue +136 -0
  26. package/dist/runtime/components/forms/RadioGroup.vue.d.ts +118 -0
  27. package/dist/runtime/components/forms/Range.vue.d.ts +3 -3
  28. package/dist/runtime/components/forms/Select.vue.d.ts +2 -2
  29. package/dist/runtime/components/forms/SelectMenu.vue +62 -57
  30. package/dist/runtime/components/forms/SelectMenu.vue.d.ts +6 -5
  31. package/dist/runtime/components/forms/Textarea.vue +5 -1
  32. package/dist/runtime/components/forms/Textarea.vue.d.ts +11 -2
  33. package/dist/runtime/components/forms/Toggle.vue.d.ts +2 -2
  34. package/dist/runtime/components/layout/Divider.vue +107 -0
  35. package/dist/runtime/components/layout/Divider.vue.d.ts +82 -0
  36. package/dist/runtime/components/navigation/CommandPalette.vue +10 -3
  37. package/dist/runtime/components/navigation/CommandPalette.vue.d.ts +1 -1
  38. package/dist/runtime/components/navigation/Pagination.vue +65 -9
  39. package/dist/runtime/components/navigation/Pagination.vue.d.ts +41 -3
  40. package/dist/runtime/components/navigation/Tabs.vue +1 -1
  41. package/dist/runtime/components/overlays/ContextMenu.vue +7 -2
  42. package/dist/runtime/components/overlays/ContextMenu.vue.d.ts +2 -1
  43. package/dist/runtime/components/overlays/Modal.vue +1 -1
  44. package/dist/runtime/components/overlays/Modal.vue.d.ts +1 -1
  45. package/dist/runtime/components/overlays/Notification.vue +7 -1
  46. package/dist/runtime/components/overlays/Notifications.vue +1 -1
  47. package/dist/runtime/components/overlays/Popover.vue +22 -7
  48. package/dist/runtime/components/overlays/Popover.vue.d.ts +14 -0
  49. package/dist/runtime/components/overlays/Slideover.vue.d.ts +1 -1
  50. package/dist/runtime/components/overlays/Tooltip.vue +16 -10
  51. package/dist/runtime/components/overlays/Tooltip.vue.d.ts +1 -0
  52. package/dist/runtime/composables/useFormGroup.d.ts +3 -3
  53. package/dist/runtime/composables/useFormGroup.mjs +17 -6
  54. package/dist/runtime/composables/usePopper.d.ts +3 -3
  55. package/dist/runtime/composables/usePopper.mjs +7 -1
  56. package/dist/runtime/composables/useUI.d.ts +2 -2
  57. package/dist/runtime/composables/useUI.mjs +2 -2
  58. package/dist/runtime/types/command-palette.d.ts +1 -0
  59. package/dist/runtime/types/form.d.ts +13 -6
  60. package/dist/runtime/types/index.d.ts +2 -0
  61. package/dist/runtime/types/meter.d.ts +5 -0
  62. package/dist/runtime/types/popper.d.ts +1 -0
  63. package/dist/runtime/types/progress.d.ts +4 -0
  64. package/dist/runtime/types/utils.d.ts +1 -1
  65. package/dist/runtime/ui.config.d.ts +263 -0
  66. package/dist/runtime/ui.config.mjs +263 -19
  67. package/dist/types.d.mts +1 -1
  68. package/dist/types.d.ts +1 -1
  69. package/package.json +19 -19
  70. package/dist/runtime/utils/StateEmitter.d.ts +0 -17
  71. package/dist/runtime/utils/StateEmitter.mjs +0 -21
@@ -0,0 +1,284 @@
1
+ <template>
2
+ <div :class="ui.wrapper" v-bind="attrs">
3
+ <slot v-if="indicator || $slots.indicator" name="indicator" v-bind="{ percent }">
4
+ <div v-if="!isSteps" :class="indicatorContainerClass" :style="{ width: `${percent}%` }">
5
+ <div :class="indicatorClass">
6
+ {{ Math.round(percent) }}%
7
+ </div>
8
+ </div>
9
+ </slot>
10
+
11
+ <progress :class="progressClass" v-bind="{ value, max: realMax }">
12
+ {{ Math.round(percent) }}%
13
+ </progress>
14
+
15
+ <div v-if="isSteps" :class="stepsClass">
16
+ <div v-for="(step, index) in max" :key="index" :class="stepClasses(index)">
17
+ <slot :name="`step-${index}`" v-bind="{ step }">
18
+ {{ step }}
19
+ </slot>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </template>
24
+
25
+ <script>
26
+ import { computed, defineComponent, toRef } from "vue";
27
+ import { twJoin } from "tailwind-merge";
28
+ import { useUI } from "../../composables/useUI";
29
+ import { mergeConfig } from "../../utils";
30
+ import appConfig from "#build/app.config";
31
+ import { progress } from "#ui/ui.config";
32
+ const config = mergeConfig(appConfig.ui.strategy, appConfig.ui.progress, progress);
33
+ export default defineComponent({
34
+ inheritAttrs: false,
35
+ props: {
36
+ value: {
37
+ type: [Number, null, void 0],
38
+ default: null
39
+ },
40
+ max: {
41
+ type: [Number, Array],
42
+ default: 100
43
+ },
44
+ indicator: {
45
+ type: Boolean,
46
+ default: false
47
+ },
48
+ animation: {
49
+ type: String,
50
+ default: () => config.default.animation,
51
+ validator(value) {
52
+ return Object.keys(config.animation).includes(value);
53
+ }
54
+ },
55
+ size: {
56
+ type: String,
57
+ default: () => config.default.size,
58
+ validator(value) {
59
+ return Object.keys(config.progress.size).includes(value);
60
+ }
61
+ },
62
+ color: {
63
+ type: String,
64
+ default: () => config.default.color,
65
+ validator(value) {
66
+ return appConfig.ui.colors.includes(value);
67
+ }
68
+ },
69
+ class: {
70
+ type: [String, Object, Array],
71
+ default: void 0
72
+ },
73
+ ui: {
74
+ type: Object,
75
+ default: void 0
76
+ }
77
+ },
78
+ setup(props) {
79
+ const { ui, attrs } = useUI("progress", toRef(props, "ui"), config, toRef(props, "class"));
80
+ const indicatorContainerClass = computed(() => {
81
+ return twJoin(
82
+ ui.value.indicator.container.base,
83
+ ui.value.indicator.container.width,
84
+ ui.value.indicator.container.transition
85
+ );
86
+ });
87
+ const indicatorClass = computed(() => {
88
+ return twJoin(
89
+ ui.value.indicator.align,
90
+ ui.value.indicator.width,
91
+ ui.value.indicator.color,
92
+ ui.value.indicator.size[props.size]
93
+ );
94
+ });
95
+ const progressClass = computed(() => {
96
+ const classes = [
97
+ ui.value.progress.base,
98
+ ui.value.progress.width,
99
+ ui.value.progress.size[props.size],
100
+ ui.value.progress.rounded,
101
+ ui.value.progress.track,
102
+ ui.value.progress.bar,
103
+ // Intermediate class to allow thumb ring or background color (set to `current`) as it's impossible to safelist with arbitrary values
104
+ ui.value.progress.color?.replaceAll("{color}", props.color),
105
+ ui.value.progress.background,
106
+ ui.value.progress.indeterminate.base,
107
+ ui.value.progress.indeterminate.rounded
108
+ ];
109
+ if (isIndeterminate.value) {
110
+ classes.push(ui.value.animation[props.animation]);
111
+ }
112
+ return twJoin(...classes);
113
+ });
114
+ const stepsClass = computed(() => {
115
+ return twJoin(
116
+ ui.value.steps.base,
117
+ ui.value.steps.color?.replaceAll("{color}", props.color),
118
+ ui.value.steps.size[props.size]
119
+ );
120
+ });
121
+ const stepClass = computed(() => {
122
+ return twJoin(
123
+ ui.value.step.base,
124
+ ui.value.step.align
125
+ );
126
+ });
127
+ const stepActiveClass = computed(() => {
128
+ return twJoin(
129
+ ui.value.step.active
130
+ );
131
+ });
132
+ const stepFirstClass = computed(() => {
133
+ return twJoin(
134
+ ui.value.step.first
135
+ );
136
+ });
137
+ function isActive(index) {
138
+ return index === Number(props.value);
139
+ }
140
+ function isFirst(index) {
141
+ return index === 0;
142
+ }
143
+ function stepClasses(index) {
144
+ index = Number(index);
145
+ const classes = [stepClass.value];
146
+ if (isFirst(index)) {
147
+ classes.push(stepFirstClass.value);
148
+ }
149
+ if (isActive(index)) {
150
+ classes.push(stepActiveClass.value);
151
+ }
152
+ return classes.join(" ");
153
+ }
154
+ const isIndeterminate = computed(() => [void 0, null].includes(props.value));
155
+ const isSteps = computed(() => Array.isArray(props.max));
156
+ const realMax = computed(() => {
157
+ if (isIndeterminate.value) {
158
+ return null;
159
+ }
160
+ if (Array.isArray(props.max)) {
161
+ return props.max.length - 1;
162
+ }
163
+ return Number(props.max);
164
+ });
165
+ const percent = computed(() => {
166
+ switch (true) {
167
+ case props.value < 0:
168
+ return 0;
169
+ case props.value > 100:
170
+ return 100;
171
+ default:
172
+ return props.value / realMax.value * 100;
173
+ }
174
+ });
175
+ return {
176
+ // eslint-disable-next-line vue/no-dupe-keys
177
+ ui,
178
+ attrs,
179
+ indicatorContainerClass,
180
+ indicatorClass,
181
+ progressClass,
182
+ stepsClass,
183
+ stepClasses,
184
+ isIndeterminate,
185
+ isSteps,
186
+ realMax,
187
+ percent
188
+ };
189
+ }
190
+ });
191
+ </script>
192
+
193
+ <style scoped>
194
+ /** These styles are required to animate the bar */
195
+ progress:indeterminate {
196
+ @apply relative;
197
+
198
+ &:after {
199
+ @apply content-[''];
200
+ @apply absolute inset-0;
201
+ @apply bg-current;
202
+ }
203
+
204
+ &::-webkit-progress-value {
205
+ @apply bg-current;
206
+ }
207
+
208
+ &::-moz-progress-bar {
209
+ @apply bg-current;
210
+ }
211
+
212
+ &.bar-animation-carousel {
213
+ &:after {
214
+ animation: carousel 2s ease-in-out infinite;
215
+ }
216
+ &::-webkit-progress-value {
217
+ animation: carousel 2s ease-in-out infinite;
218
+ }
219
+ &::-moz-progress-bar {
220
+ animation: carousel 2s ease-in-out infinite;
221
+ }
222
+ }
223
+
224
+ &.bar-animation-carousel-inverse {
225
+ &:after {
226
+ animation: carousel-inverse 2s ease-in-out infinite;
227
+ }
228
+ &::-webkit-progress-value {
229
+ animation: carousel-inverse 2s ease-in-out infinite;
230
+ }
231
+ &::-moz-progress-bar {
232
+ animation: carousel-inverse 2s ease-in-out infinite;
233
+ }
234
+ }
235
+
236
+ &.bar-animation-swing {
237
+ &:after {
238
+ animation: swing 3s ease-in-out infinite;
239
+ }
240
+ &::-webkit-progress-value {
241
+ animation: swing 3s ease-in-out infinite;
242
+ }
243
+ &::-moz-progress-bar {
244
+ animation: swing 3s ease-in-out infinite;
245
+ }
246
+ }
247
+
248
+ &.bar-animation-elastic {
249
+ &::after {
250
+ animation: elastic 3s ease-in-out infinite;
251
+ }
252
+ &::-webkit-progress-value {
253
+ animation: elastic 3s ease-in-out infinite;
254
+ }
255
+ &::-moz-progress-bar {
256
+ animation: elastic 3s ease-in-out infinite;
257
+ }
258
+ }
259
+ }
260
+
261
+ @keyframes carousel {
262
+ 0%, 100% { width: 50% }
263
+ 0% { transform: translateX(-100%) }
264
+ 100% { transform: translateX(200%) }
265
+ }
266
+
267
+ @keyframes carousel-inverse {
268
+ 0%, 100% { width: 50% }
269
+ 0% { transform: translateX(200%) }
270
+ 100% { transform: translateX(-100%) }
271
+ }
272
+
273
+ @keyframes swing {
274
+ 0%, 100% { width: 50% }
275
+ 0%, 100% { transform: translateX(-25%) }
276
+ 50% { transform: translateX(125%) }
277
+ }
278
+
279
+ @keyframes elastic {
280
+ /* Firefox doesn't do "margin: 0 auto", we have to play with margin-left */
281
+ 0%, 100% { width: 50%; margin-left: 25%; }
282
+ 50% { width: 90%; margin-left: 5% }
283
+ }
284
+ </style>
@@ -0,0 +1,2 @@
1
+ declare const _default: (props: Record<string, any> & {}) => any;
2
+ export default _default;
@@ -63,8 +63,8 @@ declare const _default: import("vue").DefineComponent<{
63
63
  [x: string]: unknown;
64
64
  }, string>>;
65
65
  toggle: import("vue").WritableComputedRef<boolean | unknown[]>;
66
- inputId: import("vue").ComputedRef<any>;
67
- name: import("vue").ComputedRef<any>;
66
+ inputId: import("vue").Ref<string | undefined>;
67
+ name: import("vue").ComputedRef<string | undefined>;
68
68
  inputClass: import("vue").ComputedRef<string>;
69
69
  onChange: (event: Event) => void;
70
70
  }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("change" | "update:modelValue")[], "change" | "update:modelValue", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
@@ -8,6 +8,13 @@
8
8
  import { provide, ref, defineComponent } from "vue";
9
9
  import { useEventBus } from "@vueuse/core";
10
10
  import { uid } from "../../utils/uid";
11
+ class FormException extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.message = message;
15
+ Object.setPrototypeOf(this, FormException.prototype);
16
+ }
17
+ }
11
18
  export default defineComponent({
12
19
  props: {
13
20
  schema: {
@@ -27,7 +34,7 @@ export default defineComponent({
27
34
  default: () => ["blur", "input", "change", "submit"]
28
35
  }
29
36
  },
30
- emits: ["submit"],
37
+ emits: ["submit", "error"],
31
38
  setup(props, { expose, emit }) {
32
39
  const bus = useEventBus(`form-${uid()}`);
33
40
  bus.on(async (event) => {
@@ -38,6 +45,8 @@ export default defineComponent({
38
45
  const errors = ref([]);
39
46
  provide("form-errors", errors);
40
47
  provide("form-events", bus);
48
+ const inputs = ref({});
49
+ provide("form-inputs", inputs);
41
50
  async function getErrors() {
42
51
  let errs = await props.validate(props.state);
43
52
  if (props.schema) {
@@ -68,19 +77,35 @@ export default defineComponent({
68
77
  errors.value = await getErrors();
69
78
  }
70
79
  if (!opts.silent && errors.value.length > 0) {
71
- throw new Error(
80
+ throw new FormException(
72
81
  `Form validation failed: ${JSON.stringify(errors.value, null, 2)}`
73
82
  );
74
83
  }
75
84
  return props.state;
76
85
  }
77
86
  async function onSubmit(event) {
78
- if (props.validateOn?.includes("submit")) {
79
- await validate();
87
+ try {
88
+ if (props.validateOn?.includes("submit")) {
89
+ await validate();
90
+ }
91
+ const submitEvent = {
92
+ ...event,
93
+ data: props.state
94
+ };
95
+ emit("submit", submitEvent);
96
+ } catch (error) {
97
+ if (!(error instanceof FormException)) {
98
+ throw error;
99
+ }
100
+ const errorEvent = {
101
+ ...event,
102
+ errors: errors.value.map((err) => ({
103
+ ...err,
104
+ id: inputs.value[err.path]
105
+ }))
106
+ };
107
+ emit("error", errorEvent);
80
108
  }
81
- const submitEvent = event;
82
- submitEvent.data = props.state;
83
- emit("submit", event);
84
109
  }
85
110
  expose({
86
111
  validate,
@@ -23,7 +23,7 @@ declare const _default: import("vue").DefineComponent<{
23
23
  };
24
24
  }, {
25
25
  onSubmit: (event: SubmitEvent) => Promise<void>;
26
- }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "submit"[], "submit", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
26
+ }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("error" | "submit")[], "error" | "submit", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
27
27
  schema: {
28
28
  type: PropType<ZodSchema<any, import("zod").ZodTypeDef, any>> | PropType<YupObjectSchema<any, import("yup").AnyObject, any, "">> | PropType<JoiSchema> | PropType<ValibotObjectSchema<any>>;
29
29
  default: undefined;
@@ -41,6 +41,7 @@ declare const _default: import("vue").DefineComponent<{
41
41
  default: () => string[];
42
42
  };
43
43
  }>> & {
44
+ onError?: ((...args: any[]) => any) | undefined;
44
45
  onSubmit?: ((...args: any[]) => any) | undefined;
45
46
  }, {
46
47
  schema: ZodSchema<any, import("zod").ZodTypeDef, any> | YupObjectSchema<any, import("yup").AnyObject, any, ""> | import("joi").AnySchema<any> | ValibotObjectSchema<any>;
@@ -79,6 +79,10 @@ export default defineComponent({
79
79
  type: Boolean,
80
80
  default: false
81
81
  },
82
+ autofocusDelay: {
83
+ type: Number,
84
+ default: 100
85
+ },
82
86
  icon: {
83
87
  type: String,
84
88
  default: null
@@ -169,7 +173,7 @@ export default defineComponent({
169
173
  onMounted(() => {
170
174
  setTimeout(() => {
171
175
  autoFocus();
172
- }, 100);
176
+ }, props.autofocusDelay);
173
177
  });
174
178
  const inputClass = computed(() => {
175
179
  const variant = ui.value.color?.[color.value]?.[props.variant] || ui.value.variant[props.variant];
@@ -32,6 +32,10 @@ declare const _default: import("vue").DefineComponent<{
32
32
  type: BooleanConstructor;
33
33
  default: boolean;
34
34
  };
35
+ autofocusDelay: {
36
+ type: NumberConstructor;
37
+ default: number;
38
+ };
35
39
  icon: {
36
40
  type: StringConstructor;
37
41
  default: null;
@@ -96,8 +100,8 @@ declare const _default: import("vue").DefineComponent<{
96
100
  attrs: import("vue").ComputedRef<Pick<{
97
101
  [x: string]: unknown;
98
102
  }, string>>;
99
- name: import("vue").ComputedRef<any>;
100
- inputId: import("vue").ComputedRef<any>;
103
+ name: import("vue").ComputedRef<string | undefined>;
104
+ inputId: import("vue").Ref<string | undefined>;
101
105
  input: import("vue").Ref<HTMLInputElement | null>;
102
106
  isLeading: import("vue").ComputedRef<string | true>;
103
107
  isTrailing: import("vue").ComputedRef<string | true>;
@@ -143,6 +147,10 @@ declare const _default: import("vue").DefineComponent<{
143
147
  type: BooleanConstructor;
144
148
  default: boolean;
145
149
  };
150
+ autofocusDelay: {
151
+ type: NumberConstructor;
152
+ default: number;
153
+ };
146
154
  icon: {
147
155
  type: StringConstructor;
148
156
  default: null;
@@ -212,9 +220,9 @@ declare const _default: import("vue").DefineComponent<{
212
220
  ui: Partial<any>;
213
221
  name: string;
214
222
  class: any;
215
- type: string;
216
223
  modelValue: string | number;
217
224
  loading: boolean;
225
+ type: string;
218
226
  disabled: boolean;
219
227
  variant: any;
220
228
  required: boolean;
@@ -228,5 +236,6 @@ declare const _default: import("vue").DefineComponent<{
228
236
  leading: boolean;
229
237
  autofocus: boolean;
230
238
  inputClass: string;
239
+ autofocusDelay: number;
231
240
  }, {}>;
232
241
  export default _default;
@@ -2,7 +2,7 @@
2
2
  <div :class="ui.wrapper">
3
3
  <div class="flex items-center h-5">
4
4
  <input
5
- :id="inputId"
5
+ :id="id"
6
6
  v-model="pick"
7
7
  :name="name"
8
8
  :required="required"
@@ -14,8 +14,8 @@
14
14
  v-bind="attrs"
15
15
  >
16
16
  </div>
17
- <div v-if="label || $slots.label" class="ms-3 text-sm">
18
- <label :for="inputId" :class="ui.label">
17
+ <div v-if="label || $slots.label" class="ms-3 inline-flex items-center">
18
+ <label :for="id" :class="ui.label">
19
19
  <slot name="label">{{ label }}</slot>
20
20
  <span v-if="required" :class="ui.required">*</span>
21
21
  </label>
@@ -27,14 +27,14 @@
27
27
  </template>
28
28
 
29
29
  <script>
30
- import { computed, toRef, defineComponent } from "vue";
30
+ import { computed, defineComponent, inject, toRef } from "vue";
31
31
  import { twMerge, twJoin } from "tailwind-merge";
32
32
  import { useUI } from "../../composables/useUI";
33
- import { useFormGroup } from "../../composables/useFormGroup";
34
33
  import { mergeConfig } from "../../utils";
35
34
  import appConfig from "#build/app.config";
36
35
  import { radio } from "#ui/ui.config";
37
36
  import { uid } from "../../utils/uid";
37
+ import { useFormGroup } from "../../composables/useFormGroup";
38
38
  const config = mergeConfig(appConfig.ui.strategy, appConfig.ui.radio, radio);
39
39
  export default defineComponent({
40
40
  inheritAttrs: false,
@@ -92,17 +92,19 @@ export default defineComponent({
92
92
  default: void 0
93
93
  }
94
94
  },
95
- emits: ["update:modelValue"],
95
+ emits: ["update:modelValue", "change"],
96
96
  setup(props, { emit }) {
97
97
  const { ui, attrs } = useUI("radio", toRef(props, "ui"), config, toRef(props, "class"));
98
- const { emitFormChange, color, name, inputId } = useFormGroup(props);
98
+ const radioGroup = inject("radio-group", null);
99
+ const { emitFormChange, color, name } = radioGroup ?? useFormGroup(props, config);
99
100
  const pick = computed({
100
101
  get() {
101
102
  return props.modelValue;
102
103
  },
103
104
  set(value) {
104
105
  emit("update:modelValue", value);
105
- if (value) {
106
+ emit("change", value);
107
+ if (!radioGroup) {
106
108
  emitFormChange();
107
109
  }
108
110
  }
@@ -119,7 +121,6 @@ export default defineComponent({
119
121
  return {
120
122
  // eslint-disable-next-line vue/no-dupe-keys
121
123
  ui,
122
- inputId,
123
124
  attrs,
124
125
  pick,
125
126
  // eslint-disable-next-line vue/no-dupe-keys
@@ -51,14 +51,13 @@ declare const _default: import("vue").DefineComponent<{
51
51
  };
52
52
  }, {
53
53
  ui: import("vue").ComputedRef<any>;
54
- inputId: import("vue").ComputedRef<any>;
55
54
  attrs: import("vue").ComputedRef<Pick<{
56
55
  [x: string]: unknown;
57
56
  }, string>>;
58
57
  pick: import("vue").WritableComputedRef<string | number | boolean | Record<string, any>>;
59
- name: import("vue").ComputedRef<any>;
58
+ name: import("vue").ComputedRef<string | undefined>;
60
59
  inputClass: import("vue").ComputedRef<string>;
61
- }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "update:modelValue"[], "update:modelValue", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
60
+ }, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("change" | "update:modelValue")[], "change" | "update:modelValue", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
62
61
  id: {
63
62
  type: StringConstructor;
64
63
  default: () => string;
@@ -110,6 +109,7 @@ declare const _default: import("vue").DefineComponent<{
110
109
  };
111
110
  }>> & {
112
111
  "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
112
+ onChange?: ((...args: any[]) => any) | undefined;
113
113
  }, {
114
114
  id: string;
115
115
  color: any;
@@ -0,0 +1,136 @@
1
+ <template>
2
+ <div :class="ui.wrapper">
3
+ <fieldset v-bind="attrs">
4
+ <legend v-if="legend || $slots.legend" :class="ui.legend">
5
+ <slot name="legend">
6
+ {{ legend }}
7
+ </slot>
8
+ </legend>
9
+ <URadio
10
+ v-for="option in normalizedOptions"
11
+ :key="option.value"
12
+ :label="option.label"
13
+ :model-value="modelValue"
14
+ :value="option.value"
15
+ :disabled="disabled"
16
+ :ui="uiRadio"
17
+ @change="onUpdate(option.value)"
18
+ >
19
+ <template #label>
20
+ <slot name="label" v-bind="{ option }" />
21
+ </template>
22
+ </URadio>
23
+ </fieldset>
24
+ </div>
25
+ </template>
26
+
27
+ <script>
28
+ import URadio from "./Radio.vue";
29
+ import { computed, defineComponent, provide, toRef } from "vue";
30
+ import { useUI } from "../../composables/useUI";
31
+ import { useFormGroup } from "../../composables/useFormGroup";
32
+ import { mergeConfig, get } from "../../utils";
33
+ import appConfig from "#build/app.config";
34
+ import { radioGroup, radio } from "#ui/ui.config";
35
+ const config = mergeConfig(appConfig.ui.strategy, appConfig.ui.radioGroup, radioGroup);
36
+ const configRadio = mergeConfig(appConfig.ui.strategy, appConfig.ui.radio, radio);
37
+ export default defineComponent({
38
+ components: {
39
+ URadio
40
+ },
41
+ inheritAttrs: false,
42
+ props: {
43
+ modelValue: {
44
+ type: [String, Number, Object],
45
+ default: ""
46
+ },
47
+ name: {
48
+ type: String,
49
+ default: null
50
+ },
51
+ legend: {
52
+ type: String,
53
+ default: null
54
+ },
55
+ options: {
56
+ type: Array,
57
+ default: () => []
58
+ },
59
+ optionAttribute: {
60
+ type: String,
61
+ default: "label"
62
+ },
63
+ valueAttribute: {
64
+ type: String,
65
+ default: "value"
66
+ },
67
+ disabled: {
68
+ type: Boolean,
69
+ default: false
70
+ },
71
+ color: {
72
+ type: String,
73
+ default: () => config.default.color,
74
+ validator(value) {
75
+ return appConfig.ui.colors.includes(value);
76
+ }
77
+ },
78
+ class: {
79
+ type: [String, Object, Array],
80
+ default: void 0
81
+ },
82
+ ui: {
83
+ type: Object,
84
+ default: void 0
85
+ },
86
+ uiRadio: {
87
+ type: Object,
88
+ default: void 0
89
+ }
90
+ },
91
+ emits: ["update:modelValue", "change"],
92
+ setup(props, { emit }) {
93
+ const { ui, attrs } = useUI("radioGroup", toRef(props, "ui"), config, toRef(props, "class"));
94
+ const { ui: uiRadio } = useUI("radio", toRef(props, "uiRadio"), configRadio);
95
+ const { emitFormChange, color, name } = useFormGroup(props, config);
96
+ provide("radio-group", { color, name });
97
+ const onUpdate = (value) => {
98
+ emit("update:modelValue", value);
99
+ emit("change", value);
100
+ emitFormChange();
101
+ };
102
+ const guessOptionValue = (option) => {
103
+ return get(option, props.valueAttribute, get(option, props.optionAttribute));
104
+ };
105
+ const guessOptionText = (option) => {
106
+ return get(option, props.optionAttribute, get(option, props.valueAttribute));
107
+ };
108
+ const normalizeOption = (option) => {
109
+ if (["string", "number", "boolean"].includes(typeof option)) {
110
+ return {
111
+ value: option,
112
+ label: option
113
+ };
114
+ }
115
+ return {
116
+ ...option,
117
+ value: guessOptionValue(option),
118
+ label: guessOptionText(option)
119
+ };
120
+ };
121
+ const normalizedOptions = computed(() => {
122
+ return props.options.map((option) => normalizeOption(option));
123
+ });
124
+ return {
125
+ // eslint-disable-next-line vue/no-dupe-keys
126
+ ui,
127
+ // eslint-disable-next-line vue/no-dupe-keys
128
+ uiRadio,
129
+ attrs,
130
+ normalizedOptions,
131
+ // eslint-disable-next-line vue/no-dupe-keys
132
+ onUpdate
133
+ };
134
+ }
135
+ });
136
+ </script>