srcdev-nuxt-forms 3.0.0 → 4.1.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 (87) hide show
  1. package/assets/styles/ally/_utils.css +20 -0
  2. package/assets/styles/ally/_variables.css +8 -0
  3. package/assets/styles/ally/index.css +2 -0
  4. package/assets/styles/forms/index.css +2 -0
  5. package/assets/styles/forms/themes/_error.css +85 -0
  6. package/assets/styles/forms/themes/_ghost.css +85 -0
  7. package/assets/styles/forms/themes/_input-action-underlined.css +20 -0
  8. package/assets/styles/forms/themes/_input-action.css +20 -0
  9. package/assets/styles/forms/themes/_primary.css +92 -0
  10. package/assets/styles/forms/themes/_secondary.css +85 -0
  11. package/assets/styles/forms/themes/_success.css +85 -0
  12. package/assets/styles/forms/themes/_tertiary.css +85 -0
  13. package/assets/styles/forms/themes/_warning.css +85 -0
  14. package/assets/styles/forms/themes/index.css +9 -0
  15. package/assets/styles/forms/variables/_sizes.css +71 -0
  16. package/assets/styles/forms/variables/_theme.css +11 -0
  17. package/assets/styles/forms/variables/index.css +2 -0
  18. package/assets/styles/main.css +5 -0
  19. package/assets/styles/typography/index.css +2 -0
  20. package/assets/styles/typography/utils/_font-classes.css +190 -0
  21. package/assets/styles/typography/utils/_weights.css +69 -0
  22. package/assets/styles/typography/utils/index.css +2 -0
  23. package/assets/styles/typography/variables/_colors.css +14 -0
  24. package/assets/styles/typography/variables/_reponsive-font-size.css +10 -0
  25. package/assets/styles/typography/variables/index.css +2 -0
  26. package/assets/styles/utils/_margin-helpers.css +334 -0
  27. package/assets/styles/utils/_padding-helpers.css +308 -0
  28. package/assets/styles/utils/_page.css +49 -0
  29. package/assets/styles/utils/index.css +3 -0
  30. package/assets/styles/variables/colors/_blue.css +15 -0
  31. package/assets/styles/variables/colors/_gray.css +16 -0
  32. package/assets/styles/variables/colors/_green.css +15 -0
  33. package/assets/styles/variables/colors/_orange.css +15 -0
  34. package/assets/styles/variables/colors/_red.css +15 -0
  35. package/assets/styles/variables/colors/_yellow.css +15 -0
  36. package/assets/styles/variables/colors/colors.css +6 -0
  37. package/assets/styles/variables/index.css +1 -0
  38. package/components/forms/c12/prop-validators/index.ts +38 -0
  39. package/components/forms/c12/utils.ts +14 -0
  40. package/components/forms/form-errors/InputError.vue +172 -0
  41. package/components/forms/form-errors/tests/InputError.spec.ts +67 -0
  42. package/components/forms/input-button/InputButtonCore.vue +191 -0
  43. package/components/forms/input-button/variants/InputButtonConfirm.vue +66 -0
  44. package/components/forms/input-button/variants/InputButtonSubmit.vue +62 -0
  45. package/components/forms/input-checkbox/MultipleCheckboxes.vue +203 -0
  46. package/components/forms/input-checkbox/SingleCheckbox.vue +169 -0
  47. package/components/forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +98 -0
  48. package/components/forms/input-checkbox/tests/data/tags.json +67 -0
  49. package/components/forms/input-checkbox-radio/InputCheckboxRadioButton.vue +214 -0
  50. package/components/forms/input-checkbox-radio/InputCheckboxRadioCore.vue +191 -0
  51. package/components/forms/input-checkbox-radio/InputCheckboxRadioWithLabel.vue +111 -0
  52. package/components/forms/input-number/InputNumberCore.vue +203 -0
  53. package/components/forms/input-number/variants/InputNumberDefault.vue +154 -0
  54. package/components/forms/input-radio/MultipleRadiobuttons.vue +201 -0
  55. package/components/forms/input-radio/tests/MultipleRadioButtons.spec.ts +89 -0
  56. package/components/forms/input-radio/tests/data/tags.json +67 -0
  57. package/components/forms/input-range/InputRangeCore.vue +274 -0
  58. package/components/forms/input-range/variants/InputRangeDefault.vue +156 -0
  59. package/components/forms/input-range-fancy/InputRangeFancyCore.vue +450 -0
  60. package/components/forms/input-range-fancy/InputRangeFancyWithLabel.vue +124 -0
  61. package/components/forms/input-select/InputSelect.vue +289 -0
  62. package/components/forms/input-text/InputTextCore.vue +331 -0
  63. package/components/forms/input-text/variants/InputPasswordWithLabel.vue +130 -0
  64. package/components/forms/input-text/variants/InputTextAsNumberWithLabel.vue +187 -0
  65. package/components/forms/input-text/variants/InputTextWithLabel.vue +298 -0
  66. package/components/forms/input-textarea/InputTextareaCore.vue +234 -0
  67. package/components/forms/input-textarea/variants/InputTextareaWithLabel.vue +267 -0
  68. package/components/forms/toggle-switch/ToggleSwitchCore.vue +198 -0
  69. package/components/forms/toggle-switch/ToggleSwitchCoreOld.vue +216 -0
  70. package/components/forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +105 -0
  71. package/components/forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +102 -0
  72. package/components/forms/ui/FormField.vue +78 -0
  73. package/components/forms/ui/FormWrapper.vue +35 -0
  74. package/components/utils/colour-scheme-select/ColourSchemeSelect.vue +270 -0
  75. package/components/utils/colour-scheme-select/ColourSchemeSelectOld.vue +225 -0
  76. package/components/utils/dark-mode-switcher/DarkModeSwitcher.vue +47 -0
  77. package/composables/useApiRequest.ts +25 -0
  78. package/composables/useColourScheme.ts +25 -0
  79. package/composables/useErrorMessages.ts +59 -0
  80. package/composables/useFormControl.ts +248 -0
  81. package/composables/useSleep.ts +5 -0
  82. package/composables/useStyleClassPassthrough.ts +30 -0
  83. package/composables/useZodValidation.ts +148 -0
  84. package/nuxt.config.ts +0 -3
  85. package/package.json +1 -1
  86. package/types/types.forms.ts +217 -0
  87. package/types/types.zodFormControl.ts +21 -0
@@ -0,0 +1,214 @@
1
+ <template>
2
+ <div class="input-checkbox-radio-button-button" :data-form-theme="formTheme" :data-size="size" :class="[size, elementClasses, optionsLayout, { error: fieldHasError }]">
3
+ <InputCheckboxRadioCore :isButton="true" :type :id :name :required v-model="modelValue" :size :trueValue :falseValue :fieldHasError :theme :ariaDescribedby>
4
+ <template #checkedIcon>
5
+ <slot name="checkedIcon"></slot>
6
+ </template>
7
+ </InputCheckboxRadioCore>
8
+ <label v-if="hasLabelContent" class="input-checkbox-radio-button-label" :for="id">
9
+ <slot name="labelContent"></slot>
10
+ </label>
11
+ <label v-else class="input-checkbox-radio-button-label" :for="id">{{ label }}</label>
12
+ <div class="item-icon">
13
+ <slot name="itemIcon">
14
+ <Icon name="material-symbols:add-2" class="icon" />
15
+ </slot>
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import propValidators from '../c12/prop-validators';
22
+
23
+ const props = defineProps({
24
+ type: {
25
+ type: String as PropType<'checkbox' | 'radio'>,
26
+ required: true,
27
+ },
28
+ id: {
29
+ type: String,
30
+ required: true,
31
+ },
32
+ name: {
33
+ type: String,
34
+ required: true,
35
+ },
36
+ label: {
37
+ type: String,
38
+ required: true,
39
+ },
40
+ required: {
41
+ type: Boolean,
42
+ default: false,
43
+ },
44
+ fieldHasError: {
45
+ type: Boolean,
46
+ default: false,
47
+ },
48
+ trueValue: {
49
+ type: [String, Number, Boolean],
50
+ default: true,
51
+ },
52
+ falseValue: {
53
+ type: [String, Number, Boolean],
54
+ default: false,
55
+ },
56
+ size: {
57
+ type: String as PropType<string>,
58
+ default: 'medium',
59
+ validator(value: string) {
60
+ return propValidators.size.includes(value);
61
+ },
62
+ },
63
+ optionsLayout: {
64
+ type: String as PropType<string>,
65
+ default: 'equal-widths',
66
+ validator(value: string) {
67
+ return propValidators.optionsLayout.includes(value);
68
+ },
69
+ },
70
+ styleClassPassthrough: {
71
+ type: Array as PropType<string[]>,
72
+ default: () => [],
73
+ },
74
+ theme: {
75
+ type: String as PropType<string>,
76
+ default: 'primary',
77
+ validator(value: string) {
78
+ return propValidators.theme.includes(value);
79
+ },
80
+ },
81
+ direction: {
82
+ type: String as PropType<'row' | 'row-reverse'>,
83
+ default: 'row',
84
+ validator(value: string) {
85
+ return ['row', 'row-reverse'].includes(value);
86
+ },
87
+ },
88
+ ariaDescribedby: {
89
+ type: String,
90
+ default: null,
91
+ },
92
+ });
93
+
94
+ const slots = useSlots();
95
+ const hasLabelContent = computed(() => slots.labelContent !== undefined);
96
+ const { elementClasses, updateElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
97
+
98
+ const modelValue = defineModel();
99
+
100
+ const formTheme = computed(() => {
101
+ return props.fieldHasError ? 'error' : props.theme;
102
+ });
103
+
104
+ const flexDirection = ref(props.direction);
105
+ </script>
106
+
107
+ <style lang="css">
108
+ .input-checkbox-radio-button-button {
109
+ /* --_checkbox-size: initial; */
110
+ --_background-color: var(--theme-checkbox-radio-button-bg-default);
111
+ --_outline-width: var(--form-element-outline-width);
112
+ --_border-color: var(--theme-checkbox-radio-button-border-default);
113
+ --_outline-color: var(--theme-checkbox-radio-button-outline-default);
114
+ --_label-color: var(--theme-checkbox-radio-button-label-default);
115
+ --_box-shadow: var(--theme-checkbox-radio-button-shadow);
116
+ --_white-space: wrap;
117
+ --_gap: 0.4rem;
118
+ /* --_border-radius: 2.2rem; */
119
+ --_padding-block: 0.4rem;
120
+ --_padding-inline: 1.2rem;
121
+
122
+ display: flex;
123
+ flex-direction: v-bind(flexDirection);
124
+ align-items: center;
125
+ justify-content: space-between;
126
+ gap: var(--_gap);
127
+
128
+ background-color: var(--_background-color);
129
+ border-radius: calc(var(--form-element-line-height) / 1);
130
+ border: var(--theme-checkbox-radio-button-border-width) solid var(--_border-color);
131
+ outline: var(--theme-checkbox-radio-button-outline-width) solid var(--_outline-color);
132
+ box-shadow: 0.1rem 0.1rem 0.8rem 0.1rem var(--_box-shadow);
133
+ padding-block: var(--_padding-block);
134
+ padding-inline: var(--_padding-inline);
135
+
136
+ &.inline {
137
+ --_white-space: nowrap;
138
+ }
139
+
140
+ /* &:focus-within {
141
+ --_box-shadow: var(--theme-checkbox-radio-button-shadow-focus);
142
+ --_outline-color: var(--theme-checkbox-radio-button-outline-focus);
143
+ } */
144
+
145
+ &:has(.input-checkbox-radio-core:focus-visible) {
146
+ --_box-shadow: var(--theme-checkbox-radio-button-shadow-focus);
147
+ --_outline-color: var(--theme-checkbox-radio-button-outline-focus);
148
+ }
149
+
150
+ /* Sizes */
151
+ &.x-small {
152
+ /* --_checkbox-size: 2rem; */
153
+ --_gap: 1rem;
154
+ /* --_border-radius: 2rem; */
155
+ --_padding-block: 0.2rem;
156
+ --_padding-inline: 1.6rem;
157
+ }
158
+ &.small {
159
+ /* --_checkbox-size: 2.2rem; */
160
+ --_gap: 1.2rem;
161
+ /* --_border-radius: 1.8rem; */
162
+ --_padding-block: 0rem;
163
+ --_padding-inline: 1.2rem;
164
+ }
165
+ &.normal {
166
+ /* --_checkbox-size: 3.4rem; */
167
+ --_gap: 1rem;
168
+ /* --_border-radius: 2rem; */
169
+ --_padding-block: 0.4rem;
170
+ --_padding-inline: 1.2rem;
171
+ }
172
+ &.medium {
173
+ /* --_checkbox-size: 3.4rem; */
174
+ --_gap: 1rem;
175
+ /* --_border-radius: 2rem; */
176
+ --_padding-block: 0.4rem;
177
+ --_padding-inline: 1.2rem;
178
+ }
179
+ &.large {
180
+ /* --_checkbox-size: 3.4rem; */
181
+ --_gap: 1rem;
182
+ /* --_border-radius: 2rem; */
183
+ --_padding-block: 0.4rem;
184
+ --_padding-inline: 1.2rem;
185
+ }
186
+ }
187
+
188
+ .input-checkbox-radio-button-label {
189
+ display: flex;
190
+ flex-grow: 1;
191
+ color: var(--_label-color);
192
+ font-size: var(--form-element-font-size);
193
+ width: 100%;
194
+ height: var(--form-element-line-height);
195
+ align-items: center;
196
+ justify-content: center;
197
+ margin-block: 0.8rem;
198
+ padding-inline: 0.8rem;
199
+ white-space: var(--_white-space);
200
+
201
+ &:hover {
202
+ cursor: pointer;
203
+ }
204
+ }
205
+
206
+ .item-icon {
207
+ display: flex;
208
+ align-items: center;
209
+ justify-content: center;
210
+ color: var(--_border-color);
211
+ height: var(--form-input-checkbox-radio-button-size);
212
+ width: var(--form-input-checkbox-radio-button-size);
213
+ }
214
+ </style>
@@ -0,0 +1,191 @@
1
+ <template>
2
+ <div class="input-checkbox-radio-wrapper" :data-form-theme="formTheme" :data-theme="size" :class="[type, size, elementClasses, { error: fieldHasError }, { button: isButton }]">
3
+ <slot name="checkedIcon" v-if="isChecked">
4
+ <Icon :name="defaultIcon" class="input-checked-icon" />
5
+ </slot>
6
+
7
+ <input
8
+ :type
9
+ :true-value
10
+ :false-value
11
+ :id
12
+ :name
13
+ :required="required && !multipleOptions"
14
+ :value="trueValue"
15
+ class="input-checkbox-radio-core"
16
+ :class="[size, { error: fieldHasError }, { 'is-button': isButton }]"
17
+ v-model="modelValue"
18
+ ref="inputField"
19
+ :aria-checked="isChecked"
20
+ :aria-describedby
21
+ :aria-invalid="fieldHasError"
22
+ />
23
+ </div>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import propValidators from '../c12/prop-validators';
28
+ const props = defineProps({
29
+ isButton: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ type: {
34
+ type: String as PropType<'checkbox' | 'radio'>,
35
+ required: true,
36
+ },
37
+ id: {
38
+ type: String,
39
+ required: true,
40
+ },
41
+ name: {
42
+ type: String,
43
+ required: true,
44
+ },
45
+ required: {
46
+ type: Boolean,
47
+ value: false,
48
+ },
49
+ trueValue: {
50
+ type: [String, Number, Boolean],
51
+ default: true,
52
+ },
53
+ falseValue: {
54
+ type: [String, Number, Boolean],
55
+ default: false,
56
+ },
57
+ multipleOptions: {
58
+ type: Boolean,
59
+ default: false,
60
+ },
61
+ theme: {
62
+ type: String as PropType<string>,
63
+ default: 'primary',
64
+ validator(value: string) {
65
+ return propValidators.theme.includes(value);
66
+ },
67
+ },
68
+ size: {
69
+ type: String as PropType<string>,
70
+ default: 'medium',
71
+ validator(value: string) {
72
+ return propValidators.size.includes(value);
73
+ },
74
+ },
75
+ fieldHasError: {
76
+ type: Boolean,
77
+ default: false,
78
+ },
79
+ styleClassPassthrough: {
80
+ type: Array as PropType<string[]>,
81
+ default: () => [],
82
+ },
83
+ ariaDescribedby: {
84
+ type: String,
85
+ default: null,
86
+ },
87
+ });
88
+
89
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
90
+
91
+ const formTheme = computed(() => {
92
+ return props.fieldHasError ? 'error' : props.theme;
93
+ });
94
+
95
+ const modelValue = defineModel<any>();
96
+
97
+ const inputField = ref<HTMLInputElement | null>(null);
98
+
99
+ const defaultIcon = computed(() => {
100
+ return props.type === 'checkbox' ? 'material-symbols:check-small' : 'material-symbols:circle';
101
+ });
102
+
103
+ const isArray = Array.isArray(modelValue.value);
104
+
105
+ const isChecked = computed(() => {
106
+ if (isArray) {
107
+ return modelValue.value.includes(props.trueValue);
108
+ } else {
109
+ return modelValue.value === props.trueValue;
110
+ }
111
+ });
112
+ </script>
113
+
114
+ <style lang="css">
115
+ .input-checkbox-radio-wrapper {
116
+ --_outline-width: 0.1rem;
117
+ --_border-width: 0.1rem;
118
+ --_border-radius: 50%;
119
+ --_background-color: var(--theme-form-checkbox-bg);
120
+ --_box-shadow: none;
121
+
122
+ --_icon-size: var(--form-input-checkbox-radio-button-size);
123
+
124
+ &:not(.button) {
125
+ --_icon-size: var(--form-toggle-symbol-size);
126
+ --_outline-width: var(--form-element-outline-width);
127
+ --_border-width: var(--form-element-border-width);
128
+ &.checkbox {
129
+ --_background-color: var(--theme-form-checkbox-bg);
130
+ --_border-color: var(--theme-form-checkbox-border);
131
+ --_border-radius: 0.4rem;
132
+ --_outline-color: var(--theme-form-checkbox-outline);
133
+ }
134
+
135
+ &.radio {
136
+ --_background-color: var(--theme-form-radio-bg);
137
+ --_border-color: var(--theme-form-radio-border);
138
+ --_border-radius: 50%;
139
+ --_outline-color: var(--theme-form-radio-outline);
140
+ }
141
+ }
142
+
143
+ display: grid;
144
+ grid-template-areas: 'element-stack';
145
+ place-content: center;
146
+
147
+ background-color: var(--_background-color);
148
+ border: var(--_border-width) solid var(--_border-color);
149
+ border-radius: var(--_border-radius);
150
+ outline: 0.1rem solid var(--_outline-color);
151
+ box-shadow: var(--_box-shadow);
152
+
153
+ height: var(--_icon-size);
154
+ width: var(--_icon-size);
155
+
156
+ transition: all 0.2s ease-in-out;
157
+
158
+ &:not(.button):has(.input-checkbox-radio-core:focus-visible) {
159
+ --_box-shadow: var(--form-focus-box-shadow);
160
+ }
161
+
162
+ .input-checked-icon {
163
+ grid-area: element-stack;
164
+ color: var(--theme-form-checkbox-symbol);
165
+ height: var(--_icon-size);
166
+ width: var(--_icon-size);
167
+ box-shadow: var(--_box-shadow);
168
+ }
169
+
170
+ .input-checkbox-radio-core {
171
+ grid-area: element-stack;
172
+ appearance: none;
173
+ margin: 0;
174
+ overflow: hidden;
175
+ opacity: 0;
176
+
177
+ height: var(--_icon-size);
178
+ width: var(--_icon-size);
179
+
180
+ &:hover {
181
+ cursor: pointer;
182
+ }
183
+
184
+ &:not(.is-button) {
185
+ &:focus-visible {
186
+ --_box-shadow: var(--form-focus-box-shadow);
187
+ }
188
+ }
189
+ }
190
+ }
191
+ </style>
@@ -0,0 +1,111 @@
1
+ <template>
2
+ <div class="input-checkbox-radio-with-label" :data-size="size" :class="[elementClasses, optionsLayout, { error: fieldHasError }]">
3
+ <InputCheckboxRadioCore :type :id :name :required v-model="modelValue" :size :trueValue :falseValue :fieldHasError :theme :ariaDescribedby>
4
+ <template #checkedIcon>
5
+ <slot name="checkedIcon"></slot>
6
+ </template>
7
+ </InputCheckboxRadioCore>
8
+ <label v-if="hasLabelContent" class="input-checkbox-radio-label body-normal" :for="id">
9
+ <slot name="labelContent"></slot>
10
+ </label>
11
+ <label v-else class="input-checkbox-radio-label body-normal-semibold" :for="id">{{ label }}</label>
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import propValidators from '../c12/prop-validators';
17
+
18
+ const props = defineProps({
19
+ type: {
20
+ type: String as PropType<'checkbox' | 'radio'>,
21
+ required: true,
22
+ },
23
+ name: {
24
+ type: String,
25
+ required: true,
26
+ },
27
+ label: {
28
+ type: String,
29
+ required: true,
30
+ },
31
+ required: {
32
+ type: Boolean,
33
+ default: false,
34
+ },
35
+ fieldHasError: {
36
+ type: Boolean,
37
+ default: false,
38
+ },
39
+ trueValue: {
40
+ type: [String, Number, Boolean],
41
+ default: true,
42
+ },
43
+ falseValue: {
44
+ type: [String, Number, Boolean],
45
+ default: false,
46
+ },
47
+ size: {
48
+ type: String as PropType<string>,
49
+ default: 'medium',
50
+ validator(value: string) {
51
+ return propValidators.size.includes(value);
52
+ },
53
+ },
54
+ optionsLayout: {
55
+ type: String as PropType<string>,
56
+ default: 'equal-widths',
57
+ validator(value: string) {
58
+ return propValidators.optionsLayout.includes(value);
59
+ },
60
+ },
61
+ styleClassPassthrough: {
62
+ type: Array as PropType<string[]>,
63
+ default: () => [],
64
+ },
65
+ theme: {
66
+ type: String as PropType<string>,
67
+ default: 'primary',
68
+ validator(value: string) {
69
+ return propValidators.theme.includes(value);
70
+ },
71
+ },
72
+ ariaDescribedby: {
73
+ type: String,
74
+ default: null,
75
+ },
76
+ });
77
+
78
+ const slots = useSlots();
79
+ const hasLabelContent = computed(() => slots.labelContent !== undefined);
80
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
81
+
82
+ const modelValue = defineModel();
83
+ const id = useId();
84
+ </script>
85
+
86
+ <style lang="css">
87
+ .input-checkbox-radio-with-label {
88
+ --_white-space: wrap;
89
+
90
+ display: flex;
91
+ align-items: center;
92
+
93
+ &.inline {
94
+ --_white-space: nowrap;
95
+ }
96
+
97
+ .input-checkbox-radio-label {
98
+ display: flex;
99
+ width: 100%;
100
+ height: 100%;
101
+ align-items: center;
102
+ margin-block: 0.8rem;
103
+ padding-inline: 0.8rem;
104
+ white-space: var(--_white-space);
105
+
106
+ &:hover {
107
+ cursor: pointer;
108
+ }
109
+ }
110
+ }
111
+ </style>
@@ -0,0 +1,203 @@
1
+ <template>
2
+ <div class="input-number-wrapper" :data-form-theme="formTheme" :data-size="size">
3
+ <div v-if="hasLeftContent" class="slot left">
4
+ <slot name="left"></slot>
5
+ </div>
6
+
7
+ <div class="input-number-container">
8
+ <input
9
+ type="number"
10
+ :id
11
+ :name
12
+ :required
13
+ :min
14
+ :max
15
+ :step
16
+ :class="[elementClasses, 'input-number-core', `input-number--${theme}`, `input-number--${size}`, `input-number--${weight}`]"
17
+ v-model="modelValue"
18
+ ref="inputField"
19
+ inputmode="numeric"
20
+ pattern="[0-9]+"
21
+ />
22
+ </div>
23
+ <div v-if="hasRightContent" class="slot right">
24
+ <slot name="right"></slot>
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import propValidators from '../c12/prop-validators';
31
+
32
+ const props = defineProps({
33
+ id: {
34
+ type: String,
35
+ required: true,
36
+ },
37
+ name: {
38
+ type: String,
39
+ required: true,
40
+ },
41
+ min: {
42
+ type: Number,
43
+ required: true,
44
+ },
45
+ max: {
46
+ type: Number,
47
+ required: true,
48
+ },
49
+ step: {
50
+ type: Number,
51
+ default: 1,
52
+ },
53
+ placeholder: {
54
+ type: String,
55
+ default: '',
56
+ },
57
+ required: {
58
+ type: Boolean,
59
+ default: false,
60
+ },
61
+ theme: {
62
+ type: String as PropType<string>,
63
+ default: 'primary',
64
+ validator(value: string) {
65
+ return propValidators.theme.includes(value);
66
+ },
67
+ },
68
+ size: {
69
+ type: String as PropType<string>,
70
+ default: 'medium',
71
+ validator(value: string) {
72
+ return propValidators.size.includes(value);
73
+ },
74
+ },
75
+ weight: {
76
+ type: String as PropType<string>,
77
+ default: 'wght-400',
78
+ validator(value: string) {
79
+ return propValidators.weight.includes(value);
80
+ },
81
+ },
82
+ fieldHasError: {
83
+ type: Boolean,
84
+ default: false,
85
+ },
86
+ styleClassPassthrough: {
87
+ type: Array as PropType<string[]>,
88
+ default: () => [],
89
+ },
90
+ });
91
+
92
+ const slots = useSlots();
93
+ const hasLeftContent = computed(() => slots.left !== undefined);
94
+ const hasRightContent = computed(() => slots.right !== undefined);
95
+
96
+ const formTheme = computed(() => {
97
+ return props.fieldHasError ? 'error' : props.theme;
98
+ });
99
+
100
+ const modelValue = defineModel<number | readonly number[]>();
101
+
102
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
103
+ const minLength = computed(() => `${props.max.toString().length + 1}em`);
104
+ </script>
105
+
106
+ <style lang="css">
107
+ .input-number-wrapper {
108
+ --_focus-box-shadow: var(--box-shadow-off);
109
+ --_min-width: v-bind(minLength);
110
+
111
+ display: flex;
112
+ align-items: center;
113
+
114
+ width: fit-content;
115
+
116
+ background-color: var(--theme-form-input-bg-normal);
117
+ border-radius: var(--form-element-border-width);
118
+ border: var(--form-element-border-width) solid var(--theme-form-input-border);
119
+ outline: var(--form-element-outline-width) solid var(--theme-form-input-outline);
120
+ box-shadow: var(--_focus-box-shadow);
121
+
122
+ .slot {
123
+ display: inline-block;
124
+
125
+ .icon {
126
+ font-weight: 900;
127
+ }
128
+ }
129
+
130
+ &.has-left-slot {
131
+ .left-slot {
132
+ display: flex;
133
+ align-items: center;
134
+ }
135
+ }
136
+
137
+ &.has-right-slot {
138
+ .right-slot {
139
+ display: flex;
140
+ align-items: center;
141
+ }
142
+ }
143
+
144
+ .input-number-core {
145
+ background-color: transparent;
146
+ border: none;
147
+ outline: none;
148
+ box-shadow: none;
149
+
150
+ background-color: var(--theme-form-input-bg-normal);
151
+ color: var(--theme-form-input-text-color-normal);
152
+ font-family: var(--font-family);
153
+ font-size: var(--form-element-font-size);
154
+ line-height: var(--form-element-line-height);
155
+
156
+ padding-inline: var(--form-text-padding-inline);
157
+ padding-block-start: var(--form-element-padding-block-start);
158
+ padding-block-end: var(--form-element-padding-block-end);
159
+ text-align: center;
160
+ min-width: var(--_min-width);
161
+
162
+ &:focus-visible {
163
+ --_focus-box-shadow: var(--box-shadow-on);
164
+ }
165
+
166
+ &::placeholder,
167
+ &::-webkit-input-placeholder {
168
+ font-family: var(--font-family);
169
+ font-size: var(--font-size);
170
+ font-style: italic;
171
+ font-weight: 400;
172
+ }
173
+ }
174
+
175
+ &:has(.has-left-button),
176
+ &:has(.has-right-button) {
177
+ .slot {
178
+ .input-button-core {
179
+ border: initial;
180
+ border-radius: 0;
181
+ outline: initial;
182
+ box-shadow: unset;
183
+ }
184
+ }
185
+
186
+ .left-slot {
187
+ margin-inline-end: 0;
188
+ border-right: 2px solid var(--theme-btn-bg-hover);
189
+ }
190
+
191
+ .right-slot {
192
+ margin-inline-end: 0;
193
+ border-left: 2px solid var(--theme-btn-bg-hover);
194
+ }
195
+ }
196
+ }
197
+
198
+ input[type='number']::-webkit-inner-spin-button,
199
+ input[type='number']::-webkit-outer-spin-button {
200
+ -webkit-appearance: none;
201
+ margin: 0;
202
+ }
203
+ </style>