base-ui-vue 0.3.0 → 0.5.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 (218) hide show
  1. package/dist/arrow/TooltipArrow.cjs +609 -0
  2. package/dist/arrow/TooltipArrow.cjs.map +1 -0
  3. package/dist/arrow/TooltipArrow.js +478 -0
  4. package/dist/arrow/TooltipArrow.js.map +1 -0
  5. package/dist/button/Button.cjs +5 -317
  6. package/dist/button/Button.cjs.map +1 -1
  7. package/dist/button/Button.js +3 -249
  8. package/dist/button/Button.js.map +1 -1
  9. package/dist/button/ToolbarButton.cjs +19 -6
  10. package/dist/button/ToolbarButton.cjs.map +1 -1
  11. package/dist/button/ToolbarButton.js +3 -2
  12. package/dist/button/ToolbarButton.js.map +1 -1
  13. package/dist/checkbox/index.cjs +20 -9
  14. package/dist/checkbox/index.cjs.map +1 -1
  15. package/dist/checkbox/index.js +7 -2
  16. package/dist/checkbox/index.js.map +1 -1
  17. package/dist/checkbox-group/CheckboxGroup.cjs +14 -13
  18. package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -1
  19. package/dist/checkbox-group/CheckboxGroup.js +2 -1
  20. package/dist/checkbox-group/CheckboxGroup.js.map +1 -1
  21. package/dist/components/FloatingPortal.cjs +66 -0
  22. package/dist/components/FloatingPortal.cjs.map +1 -0
  23. package/dist/components/FloatingPortal.js +55 -0
  24. package/dist/components/FloatingPortal.js.map +1 -0
  25. package/dist/components/FloatingRootStore.cjs +101 -0
  26. package/dist/components/FloatingRootStore.cjs.map +1 -0
  27. package/dist/components/FloatingRootStore.js +84 -0
  28. package/dist/components/FloatingRootStore.js.map +1 -0
  29. package/dist/components/FloatingTree.cjs +33 -0
  30. package/dist/components/FloatingTree.cjs.map +1 -0
  31. package/dist/components/FloatingTree.js +22 -0
  32. package/dist/components/FloatingTree.js.map +1 -0
  33. package/dist/composite/composite.cjs +6 -0
  34. package/dist/composite/composite.js +1 -1
  35. package/dist/content/ScrollAreaContent.cjs +3 -3
  36. package/dist/content/ScrollAreaContent.cjs.map +1 -1
  37. package/dist/content/ScrollAreaContent.js +1 -1
  38. package/dist/control/FieldControl.cjs +3 -3
  39. package/dist/control/FieldControl.cjs.map +1 -1
  40. package/dist/control/FieldControl.js +1 -1
  41. package/dist/control/SliderControl.cjs +3 -3
  42. package/dist/control/SliderControl.cjs.map +1 -1
  43. package/dist/control/SliderControl.js +3 -3
  44. package/dist/corner/ScrollAreaCorner.cjs +3 -3
  45. package/dist/corner/ScrollAreaCorner.cjs.map +1 -1
  46. package/dist/corner/ScrollAreaCorner.js +1 -1
  47. package/dist/decrement/NumberFieldDecrement.cjs +868 -0
  48. package/dist/decrement/NumberFieldDecrement.cjs.map +1 -0
  49. package/dist/decrement/NumberFieldDecrement.js +701 -0
  50. package/dist/decrement/NumberFieldDecrement.js.map +1 -0
  51. package/dist/description/FieldDescription.cjs +2 -2
  52. package/dist/description/FieldDescription.cjs.map +1 -1
  53. package/dist/description/FieldDescription.js +1 -1
  54. package/dist/error/FieldError.cjs +2 -2
  55. package/dist/error/FieldError.cjs.map +1 -1
  56. package/dist/error/FieldError.js +1 -1
  57. package/dist/fallback/AvatarFallback.cjs +4 -48
  58. package/dist/fallback/AvatarFallback.cjs.map +1 -1
  59. package/dist/fallback/AvatarFallback.js +4 -42
  60. package/dist/fallback/AvatarFallback.js.map +1 -1
  61. package/dist/floating-ui-vue/index.js +638 -0
  62. package/dist/floating-ui-vue/index.js.map +1 -0
  63. package/dist/form/Form.cjs +3 -3
  64. package/dist/form/Form.cjs.map +1 -1
  65. package/dist/form/Form.js +1 -1
  66. package/dist/group/NumberFieldGroup.cjs +72 -0
  67. package/dist/group/NumberFieldGroup.cjs.map +1 -0
  68. package/dist/group/NumberFieldGroup.js +67 -0
  69. package/dist/group/NumberFieldGroup.js.map +1 -0
  70. package/dist/group/ToolbarGroup.cjs +2 -2
  71. package/dist/group/ToolbarGroup.cjs.map +1 -1
  72. package/dist/group/ToolbarGroup.js +1 -1
  73. package/dist/header/AccordionHeader.cjs +2 -2
  74. package/dist/header/AccordionHeader.cjs.map +1 -1
  75. package/dist/header/AccordionHeader.js +1 -1
  76. package/dist/hooks/useDismiss.cjs +502 -0
  77. package/dist/hooks/useDismiss.cjs.map +1 -0
  78. package/dist/hooks/useHoverFloatingInteraction.cjs +233 -0
  79. package/dist/hooks/useHoverFloatingInteraction.cjs.map +1 -0
  80. package/dist/image/AvatarImage.cjs +4 -4
  81. package/dist/image/AvatarImage.cjs.map +1 -1
  82. package/dist/image/AvatarImage.js +1 -1
  83. package/dist/increment/NumberFieldIncrement.cjs +113 -0
  84. package/dist/increment/NumberFieldIncrement.cjs.map +1 -0
  85. package/dist/increment/NumberFieldIncrement.js +108 -0
  86. package/dist/increment/NumberFieldIncrement.js.map +1 -0
  87. package/dist/index.cjs +80 -8
  88. package/dist/index.d.cts +4328 -1600
  89. package/dist/index.d.cts.map +1 -1
  90. package/dist/index.d.ts +4328 -1600
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +8 -4
  93. package/dist/index2.cjs +9435 -3236
  94. package/dist/index2.cjs.map +1 -1
  95. package/dist/index2.js +8905 -3049
  96. package/dist/index2.js.map +1 -1
  97. package/dist/rolldown-runtime.js +36 -0
  98. package/package.json +1 -1
  99. package/src/floating-ui-vue/components/FloatingPortal.ts +171 -0
  100. package/src/floating-ui-vue/components/FloatingPortal.vue +259 -0
  101. package/src/floating-ui-vue/components/FloatingRootStore.ts +83 -0
  102. package/src/floating-ui-vue/components/FloatingTree.ts +64 -0
  103. package/src/floating-ui-vue/components/FloatingTreeStore.ts +21 -0
  104. package/src/floating-ui-vue/hooks/useDismiss.ts +689 -0
  105. package/src/floating-ui-vue/hooks/useHoverFloatingInteraction.ts +244 -0
  106. package/src/floating-ui-vue/hooks/useHoverInteractionSharedState.ts +125 -0
  107. package/src/floating-ui-vue/hooks/useHoverShared.ts +53 -0
  108. package/src/floating-ui-vue/index.ts +48 -0
  109. package/src/floating-ui-vue/middleware/arrow.ts +114 -0
  110. package/src/floating-ui-vue/types.ts +117 -0
  111. package/src/floating-ui-vue/utils/composite.ts +20 -0
  112. package/src/floating-ui-vue/utils/constants.ts +2 -0
  113. package/src/floating-ui-vue/utils/createAttribute.ts +3 -0
  114. package/src/floating-ui-vue/utils/createEventEmitter.ts +20 -0
  115. package/src/floating-ui-vue/utils/element.ts +59 -0
  116. package/src/floating-ui-vue/utils/event.ts +15 -0
  117. package/src/floating-ui-vue/utils/nodes.ts +14 -0
  118. package/src/floating-ui-vue/utils/tabbable.ts +309 -0
  119. package/src/index.ts +7 -1
  120. package/src/number-field/decrement/NumberFieldDecrement.vue +109 -0
  121. package/src/number-field/group/NumberFieldGroup.vue +47 -0
  122. package/src/number-field/increment/NumberFieldIncrement.vue +109 -0
  123. package/src/number-field/index.ts +42 -0
  124. package/src/number-field/input/NumberFieldInput.vue +455 -0
  125. package/src/number-field/root/NumberFieldRoot.vue +626 -0
  126. package/src/number-field/root/NumberFieldRootContext.ts +94 -0
  127. package/src/number-field/root/useNumberFieldButton.ts +171 -0
  128. package/src/number-field/scrub-area/NumberFieldScrubArea.vue +359 -0
  129. package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +26 -0
  130. package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.vue +75 -0
  131. package/src/number-field/utils/constants.ts +4 -0
  132. package/src/number-field/utils/getViewportRect.ts +34 -0
  133. package/src/number-field/utils/parse.ts +248 -0
  134. package/src/number-field/utils/stateAttributesMapping.ts +9 -0
  135. package/src/number-field/utils/subscribeToVisualViewportResize.ts +27 -0
  136. package/src/number-field/utils/types.ts +24 -0
  137. package/src/number-field/utils/validate.ts +120 -0
  138. package/src/otp-field/index.ts +22 -0
  139. package/src/otp-field/input/OtpFieldInput.vue +336 -0
  140. package/src/otp-field/root/OtpFieldRoot.vue +583 -0
  141. package/src/otp-field/root/OtpFieldRootContext.ts +81 -0
  142. package/src/otp-field/utils/otp.ts +135 -0
  143. package/src/otp-field/utils/stateAttributesMapping.ts +16 -0
  144. package/src/radio/index.ts +28 -0
  145. package/src/radio/indicator/RadioIndicator.vue +90 -0
  146. package/src/radio/indicator/RadioIndicatorDataAttributes.ts +54 -0
  147. package/src/radio/root/RadioRoot.vue +544 -0
  148. package/src/radio/root/RadioRootContext.ts +18 -0
  149. package/src/radio/root/RadioRootDataAttributes.ts +46 -0
  150. package/src/radio/utils/stateAttributesMapping.ts +29 -0
  151. package/src/radio-group/RadioGroup.vue +381 -0
  152. package/src/radio-group/RadioGroupContext.ts +42 -0
  153. package/src/radio-group/RadioGroupDataAttributes.ts +6 -0
  154. package/src/radio-group/index.ts +10 -0
  155. package/src/tabs/index.ts +38 -0
  156. package/src/tabs/indicator/TabsIndicator.vue +201 -0
  157. package/src/tabs/indicator/TabsIndicatorCssVars.ts +26 -0
  158. package/src/tabs/indicator/TabsIndicatorDataAttributes.ts +12 -0
  159. package/src/tabs/indicator/prehydrationScript.min.ts +5 -0
  160. package/src/tabs/indicator/prehydrationScript.template.js +87 -0
  161. package/src/tabs/list/TabsList.vue +201 -0
  162. package/src/tabs/list/TabsListContext.ts +29 -0
  163. package/src/tabs/list/TabsListDataAttributes.ts +12 -0
  164. package/src/tabs/panel/TabsPanel.vue +167 -0
  165. package/src/tabs/panel/TabsPanelDataAttributes.ts +28 -0
  166. package/src/tabs/root/TabsRoot.vue +468 -0
  167. package/src/tabs/root/TabsRootContext.ts +56 -0
  168. package/src/tabs/root/TabsRootDataAttributes.ts +12 -0
  169. package/src/tabs/root/stateAttributesMapping.ts +9 -0
  170. package/src/tabs/tab/TabsTab.vue +274 -0
  171. package/src/tabs/tab/TabsTabDataAttributes.ts +20 -0
  172. package/src/tabs/utils/areTabValuesEqual.ts +6 -0
  173. package/src/tooltip/arrow/TooltipArrow.vue +116 -0
  174. package/src/tooltip/arrow/TooltipArrowDataAttributes.ts +31 -0
  175. package/src/tooltip/index.ts +54 -0
  176. package/src/tooltip/popup/TooltipPopup.vue +142 -0
  177. package/src/tooltip/popup/TooltipPopupDataAttributes.ts +35 -0
  178. package/src/tooltip/portal/TooltipPortal.vue +83 -0
  179. package/src/tooltip/portal/TooltipPortalContext.ts +19 -0
  180. package/src/tooltip/positioner/TooltipPositioner.vue +221 -0
  181. package/src/tooltip/positioner/TooltipPositionerContext.ts +23 -0
  182. package/src/tooltip/positioner/TooltipPositionerCssVars.ts +27 -0
  183. package/src/tooltip/positioner/TooltipPositionerDataAttributes.ts +26 -0
  184. package/src/tooltip/provider/TooltipProvider.vue +78 -0
  185. package/src/tooltip/provider/TooltipProviderContext.ts +19 -0
  186. package/src/tooltip/root/TooltipRoot.vue +401 -0
  187. package/src/tooltip/root/TooltipRootContext.ts +71 -0
  188. package/src/tooltip/store/TooltipHandle.ts +215 -0
  189. package/src/tooltip/trigger/TooltipTrigger.vue +343 -0
  190. package/src/tooltip/trigger/TooltipTriggerDataAttributes.ts +12 -0
  191. package/src/tooltip/utils/constants.ts +2 -0
  192. package/src/tooltip/viewport/TooltipViewport.vue +149 -0
  193. package/src/tooltip/viewport/TooltipViewportCssVars.ts +14 -0
  194. package/src/tooltip/viewport/TooltipViewportDataAttributes.ts +26 -0
  195. package/src/utils/FloatingPortalLite.vue +66 -0
  196. package/src/utils/FocusGuard.vue +36 -0
  197. package/src/utils/adaptiveOriginMiddleware.ts +79 -0
  198. package/src/utils/addEventListener.ts +16 -0
  199. package/src/utils/constants.ts +11 -0
  200. package/src/utils/createBaseUIEventDetails.ts +2 -0
  201. package/src/utils/detectBrowser.ts +15 -0
  202. package/src/utils/formatNumber.ts +35 -2
  203. package/src/utils/getCssDimensions.ts +19 -0
  204. package/src/utils/getDisabledMountTransitionStyles.ts +9 -0
  205. package/src/utils/hideMiddleware.ts +19 -0
  206. package/src/utils/mergeCleanups.ts +7 -0
  207. package/src/utils/platform.ts +18 -0
  208. package/src/utils/popupStateMapping.ts +103 -0
  209. package/src/utils/reasons.ts +4 -0
  210. package/src/utils/serializeValue.ts +17 -0
  211. package/src/utils/useAnchorPositioning.ts +644 -0
  212. package/src/utils/useInterval.ts +45 -0
  213. package/src/utils/usePopupAutoResize.ts +254 -0
  214. package/src/utils/usePopupViewport.ts +398 -0
  215. package/src/utils/usePositioner.ts +61 -0
  216. package/src/utils/usePressAndHold.ts +260 -0
  217. package/src/utils/useRenderElement.ts +3 -0
  218. package/src/utils/useValueChanged.ts +21 -0
@@ -0,0 +1,109 @@
1
+ <script setup lang="ts">
2
+ import type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types'
3
+ import type { NumberFieldRootState } from '../root/NumberFieldRoot.vue'
4
+ import { computed, useAttrs } from 'vue'
5
+ import { mergeProps } from '../../merge-props/mergeProps'
6
+ import { useButton } from '../../use-button/useButton'
7
+ import { useRenderElement } from '../../utils/useRenderElement'
8
+ import { useNumberFieldRootContext } from '../root/NumberFieldRootContext'
9
+ import { useNumberFieldButton } from '../root/useNumberFieldButton'
10
+ import { stateAttributesMapping } from '../utils/stateAttributesMapping'
11
+
12
+ export type NumberFieldIncrementState = NumberFieldRootState
13
+
14
+ export interface NumberFieldIncrementProps
15
+ extends NativeButtonProps, BaseUIComponentProps<NumberFieldIncrementState> {
16
+ disabled?: boolean
17
+ }
18
+
19
+ defineOptions({
20
+ name: 'NumberFieldIncrement',
21
+ inheritAttrs: false,
22
+ })
23
+
24
+ const props = withDefaults(defineProps<NumberFieldIncrementProps>(), {
25
+ as: 'button',
26
+ disabled: false,
27
+ nativeButton: true,
28
+ })
29
+
30
+ const attrs = useAttrs()
31
+ const attrsObject = attrs as Record<string, any>
32
+
33
+ const {
34
+ allowInputSyncRef,
35
+ disabled: contextDisabled,
36
+ formatOptionsRef,
37
+ getStepAmount,
38
+ id,
39
+ incrementValue,
40
+ inputRef,
41
+ inputValue,
42
+ locale,
43
+ maxWithDefault,
44
+ readOnly,
45
+ setValue,
46
+ state,
47
+ value,
48
+ valueRef,
49
+ lastChangedValueRef,
50
+ onValueCommitted,
51
+ } = useNumberFieldRootContext()
52
+
53
+ const isMax = computed(() => value.value != null && value.value >= maxWithDefault.value)
54
+ const disabled = computed(() => props.disabled || contextDisabled.value || isMax.value)
55
+
56
+ const buttonProps = useNumberFieldButton({
57
+ isIncrement: true,
58
+ inputRef,
59
+ inputValue: () => inputValue.value,
60
+ disabled: () => disabled.value,
61
+ readOnly: () => readOnly.value,
62
+ id: () => id.value,
63
+ setValue,
64
+ getStepAmount,
65
+ incrementValue,
66
+ allowInputSyncRef,
67
+ formatOptionsRef,
68
+ valueRef,
69
+ locale: () => locale.value,
70
+ lastChangedValueRef,
71
+ onValueCommitted,
72
+ })
73
+
74
+ const { getButtonProps, buttonRef } = useButton({
75
+ disabled: () => disabled.value,
76
+ native: () => props.nativeButton,
77
+ focusableWhenDisabled: true,
78
+ })
79
+
80
+ const buttonState = computed<NumberFieldIncrementState>(() => ({
81
+ ...state.value,
82
+ disabled: disabled.value,
83
+ }))
84
+
85
+ const combinedProps = computed(() =>
86
+ mergeProps(buttonProps.value, attrsObject, getButtonProps()),
87
+ )
88
+
89
+ const {
90
+ tag,
91
+ mergedProps,
92
+ renderless,
93
+ ref: renderRef,
94
+ } = useRenderElement({
95
+ componentProps: props,
96
+ state: buttonState,
97
+ props: combinedProps,
98
+ stateAttributesMapping,
99
+ defaultTagName: 'button',
100
+ ref: buttonRef,
101
+ })
102
+ </script>
103
+
104
+ <template>
105
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="buttonState" />
106
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
107
+ <slot :state="buttonState" />
108
+ </component>
109
+ </template>
@@ -0,0 +1,42 @@
1
+ export { default as NumberFieldDecrement } from './decrement/NumberFieldDecrement.vue'
2
+ export type { NumberFieldDecrementProps, NumberFieldDecrementState } from './decrement/NumberFieldDecrement.vue'
3
+
4
+ export { default as NumberFieldGroup } from './group/NumberFieldGroup.vue'
5
+ export type { NumberFieldGroupProps, NumberFieldGroupState } from './group/NumberFieldGroup.vue'
6
+
7
+ export { default as NumberFieldIncrement } from './increment/NumberFieldIncrement.vue'
8
+ export type { NumberFieldIncrementProps, NumberFieldIncrementState } from './increment/NumberFieldIncrement.vue'
9
+
10
+ export { default as NumberFieldInput } from './input/NumberFieldInput.vue'
11
+ export type { NumberFieldInputProps, NumberFieldInputState } from './input/NumberFieldInput.vue'
12
+
13
+ export { default as NumberFieldRoot } from './root/NumberFieldRoot.vue'
14
+ export type { NumberFieldRootProps, NumberFieldRootState } from './root/NumberFieldRoot.vue'
15
+
16
+ export {
17
+ numberFieldRootContextKey,
18
+ useNumberFieldRootContext,
19
+ } from './root/NumberFieldRootContext'
20
+ export type {
21
+ InputMode,
22
+ NumberFieldRootChangeEventDetails,
23
+ NumberFieldRootChangeEventReason,
24
+ NumberFieldRootCommitEventDetails,
25
+ NumberFieldRootCommitEventReason,
26
+ NumberFieldRootContext,
27
+ } from './root/NumberFieldRootContext'
28
+
29
+ export { default as NumberFieldScrubAreaCursor } from './scrub-area-cursor/NumberFieldScrubAreaCursor.vue'
30
+ export type {
31
+ NumberFieldScrubAreaCursorProps,
32
+ NumberFieldScrubAreaCursorState,
33
+ } from './scrub-area-cursor/NumberFieldScrubAreaCursor.vue'
34
+
35
+ export { default as NumberFieldScrubArea } from './scrub-area/NumberFieldScrubArea.vue'
36
+ export type { NumberFieldScrubAreaProps, NumberFieldScrubAreaState } from './scrub-area/NumberFieldScrubArea.vue'
37
+
38
+ export {
39
+ numberFieldScrubAreaContextKey,
40
+ useNumberFieldScrubAreaContext,
41
+ } from './scrub-area/NumberFieldScrubAreaContext'
42
+ export type { NumberFieldScrubAreaContext } from './scrub-area/NumberFieldScrubAreaContext'
@@ -0,0 +1,455 @@
1
+ <script setup lang="ts">
2
+ import type { ComponentPublicInstance } from 'vue'
3
+ import type { BaseUIComponentProps } from '../../utils/types'
4
+ import type { NumberFieldRootState } from '../root/NumberFieldRoot.vue'
5
+ import { computed, useAttrs } from 'vue'
6
+ import { useFieldRootContext } from '../../field/root/FieldRootContext'
7
+ import { stopEvent } from '../../floating-ui-vue/utils'
8
+ import { useFormContext } from '../../form/FormContext'
9
+ import { useLabelableContext } from '../../labelable-provider/LabelableContext'
10
+ import { mergeProps } from '../../merge-props/mergeProps'
11
+ import {
12
+ createChangeEventDetails,
13
+ createGenericEventDetails,
14
+ } from '../../utils/createBaseUIEventDetails'
15
+ import { formatNumber, formatNumberMaxPrecision } from '../../utils/formatNumber'
16
+ import { REASONS } from '../../utils/reasons'
17
+ import { useRenderElement } from '../../utils/useRenderElement'
18
+ import { useValueChanged } from '../../utils/useValueChanged'
19
+ import { warn } from '../../utils/warn'
20
+ import { useNumberFieldRootContext } from '../root/NumberFieldRootContext'
21
+ import { DEFAULT_STEP } from '../utils/constants'
22
+ import {
23
+ ANY_MINUS_DETECT_RE,
24
+ ANY_MINUS_RE,
25
+ ANY_PLUS_DETECT_RE,
26
+ ANY_PLUS_RE,
27
+ ARABIC_DETECT_RE,
28
+ FULLWIDTH_DETECT_RE,
29
+ getNumberLocaleDetails,
30
+ HAN_DETECT_RE,
31
+ parseNumber,
32
+ PERSIAN_DETECT_RE,
33
+ } from '../utils/parse'
34
+ import { stateAttributesMapping } from '../utils/stateAttributesMapping'
35
+ import { hasNumberFormatRoundingOptions, removeFloatingPointErrors } from '../utils/validate'
36
+
37
+ export type NumberFieldInputState = NumberFieldRootState
38
+
39
+ export interface NumberFieldInputProps extends BaseUIComponentProps<NumberFieldInputState> {}
40
+
41
+ defineOptions({
42
+ name: 'NumberFieldInput',
43
+ inheritAttrs: false,
44
+ })
45
+
46
+ const props = defineProps<NumberFieldInputProps>()
47
+
48
+ const attrs = useAttrs()
49
+ const attrsObject = attrs as Record<string, any>
50
+
51
+ const NAVIGATE_KEYS = new Set([
52
+ 'Backspace',
53
+ 'Delete',
54
+ 'ArrowLeft',
55
+ 'ArrowRight',
56
+ 'Tab',
57
+ 'Enter',
58
+ 'Escape',
59
+ ])
60
+
61
+ const {
62
+ allowInputSyncRef,
63
+ disabled,
64
+ formatOptionsRef,
65
+ getAllowedNonNumericKeys,
66
+ getStepAmount,
67
+ id,
68
+ incrementValue,
69
+ inputMode,
70
+ inputValue,
71
+ max,
72
+ min,
73
+ name,
74
+ readOnly,
75
+ required,
76
+ setValue,
77
+ state,
78
+ setInputValue,
79
+ locale,
80
+ inputRef,
81
+ value,
82
+ onValueCommitted,
83
+ lastChangedValueRef,
84
+ hasPendingCommitRef,
85
+ valueRef,
86
+ } = useNumberFieldRootContext()
87
+
88
+ const { clearErrors } = useFormContext()
89
+ const {
90
+ validationMode,
91
+ setTouched,
92
+ setFocused,
93
+ invalid,
94
+ shouldValidateOnChange,
95
+ validation,
96
+ } = useFieldRootContext()
97
+ const { labelId } = useLabelableContext()
98
+
99
+ let hasTouchedInput = false
100
+ let blockRevalidation = false
101
+
102
+ function setInputRef(el: Element | ComponentPublicInstance | null) {
103
+ inputRef.value = el as HTMLInputElement | null
104
+ }
105
+
106
+ useValueChanged(value, () => {
107
+ clearErrors(name.value)
108
+
109
+ if (blockRevalidation && !shouldValidateOnChange()) {
110
+ blockRevalidation = false
111
+ return
112
+ }
113
+
114
+ void validation.commit(value.value, true)
115
+ })
116
+
117
+ function onFocus(event: FocusEvent) {
118
+ if (event.defaultPrevented || readOnly.value || disabled.value) {
119
+ return
120
+ }
121
+
122
+ setFocused(true)
123
+
124
+ if (hasTouchedInput) {
125
+ return
126
+ }
127
+
128
+ hasTouchedInput = true
129
+
130
+ // Browsers set selection at the start of the input field by default. We want to set it at
131
+ // the end for the first focus.
132
+ const target = event.currentTarget as HTMLInputElement
133
+ const length = target.value.length
134
+ target.setSelectionRange(length, length)
135
+ }
136
+
137
+ function onBlur(event: FocusEvent) {
138
+ if (event.defaultPrevented || readOnly.value || disabled.value) {
139
+ return
140
+ }
141
+
142
+ setTouched(true)
143
+ setFocused(false)
144
+
145
+ const hadManualInput = !allowInputSyncRef.value
146
+ const hadPendingProgrammaticChange = hasPendingCommitRef.value
147
+
148
+ allowInputSyncRef.value = true
149
+
150
+ if (inputValue.value.trim() === '') {
151
+ setValue(null, createChangeEventDetails(REASONS.inputClear, event))
152
+ if (validationMode.value === 'onBlur') {
153
+ void validation.commit(null)
154
+ }
155
+ onValueCommitted(null, createGenericEventDetails(REASONS.inputClear, event))
156
+ return
157
+ }
158
+
159
+ const formatOptions = formatOptionsRef.value
160
+ const parsedValue = parseNumber(inputValue.value, locale.value, formatOptions)
161
+ if (parsedValue === null) {
162
+ return
163
+ }
164
+
165
+ // Avoid applying Intl's default precision unless the format opts into rounding.
166
+ const hasRoundingOptions = hasNumberFormatRoundingOptions(formatOptions)
167
+
168
+ const committed = hasRoundingOptions
169
+ ? removeFloatingPointErrors(parsedValue, formatOptions)
170
+ : parsedValue
171
+
172
+ const nextEventDetails = createGenericEventDetails(REASONS.inputBlur, event)
173
+ const shouldUpdateValue = value.value !== committed
174
+ const shouldCommit = hadManualInput || shouldUpdateValue || hadPendingProgrammaticChange
175
+
176
+ // Use the stored value after `setValue` clamps it.
177
+ let committedValue = committed
178
+ if (shouldUpdateValue) {
179
+ const changeDetails = createChangeEventDetails(REASONS.inputBlur, event)
180
+ blockRevalidation = true
181
+ setValue(committed, changeDetails)
182
+ if (changeDetails.isCanceled) {
183
+ blockRevalidation = false
184
+ return
185
+ }
186
+ committedValue = lastChangedValueRef.value ?? committed
187
+ }
188
+ if (validationMode.value === 'onBlur') {
189
+ void validation.commit(committedValue)
190
+ }
191
+ if (shouldCommit) {
192
+ onValueCommitted(committedValue, nextEventDetails)
193
+ }
194
+
195
+ // Normalize only the displayed text
196
+ const canonicalText = formatNumber(committedValue, locale.value, formatOptions)
197
+ const shouldPreserveFullPrecision
198
+ = !hasRoundingOptions
199
+ && parsedValue === value.value
200
+ && inputValue.value === formatNumberMaxPrecision(parsedValue, locale.value, formatOptions)
201
+
202
+ if (!shouldPreserveFullPrecision && inputValue.value !== canonicalText) {
203
+ setInputValue(canonicalText)
204
+ }
205
+ }
206
+
207
+ function onInput(event: Event) {
208
+ // Workaround for https://github.com/facebook/react/issues/9023
209
+ if (event.defaultPrevented) {
210
+ return
211
+ }
212
+
213
+ allowInputSyncRef.value = false
214
+ const targetValue = (event.currentTarget as HTMLInputElement).value
215
+
216
+ if (targetValue.trim() === '') {
217
+ setInputValue(targetValue)
218
+ setValue(null, createChangeEventDetails(REASONS.inputClear, event))
219
+ return
220
+ }
221
+
222
+ // Update the input text immediately and only fire onValueChange if the typed value is
223
+ // currently parseable into a number.
224
+ const allowedNonNumericKeys = getAllowedNonNumericKeys()
225
+ const isValidCharacterString = Array.from(targetValue).every((ch) => {
226
+ const isAsciiDigit = ch >= '0' && ch <= '9'
227
+ const isArabicNumeral = ARABIC_DETECT_RE.test(ch)
228
+ const isHanNumeral = HAN_DETECT_RE.test(ch)
229
+ const isPersianNumeral = PERSIAN_DETECT_RE.test(ch)
230
+ const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(ch)
231
+ const isMinus = ANY_MINUS_DETECT_RE.test(ch)
232
+ return (
233
+ isAsciiDigit
234
+ || isArabicNumeral
235
+ || isHanNumeral
236
+ || isPersianNumeral
237
+ || isFullwidthNumeral
238
+ || isMinus
239
+ || allowedNonNumericKeys.has(ch)
240
+ )
241
+ })
242
+
243
+ if (!isValidCharacterString) {
244
+ return
245
+ }
246
+
247
+ const parsedValue = parseNumber(targetValue, locale.value, formatOptionsRef.value)
248
+
249
+ setInputValue(targetValue)
250
+
251
+ if (parsedValue !== null) {
252
+ setValue(parsedValue, createChangeEventDetails(REASONS.inputChange, event))
253
+ }
254
+ }
255
+
256
+ function onKeydown(event: KeyboardEvent) {
257
+ if (event.defaultPrevented || readOnly.value || disabled.value) {
258
+ return
259
+ }
260
+
261
+ allowInputSyncRef.value = true
262
+
263
+ const allowedNonNumericKeys = getAllowedNonNumericKeys()
264
+
265
+ let isAllowedNonNumericKey = allowedNonNumericKeys.has(event.key)
266
+
267
+ const { decimal, currency, percentSign } = getNumberLocaleDetails(
268
+ locale.value,
269
+ formatOptionsRef.value,
270
+ )
271
+
272
+ const target = event.currentTarget as HTMLInputElement
273
+ const selectionStart = target.selectionStart
274
+ const selectionEnd = target.selectionEnd
275
+ const isAllSelected = selectionStart === 0 && selectionEnd === inputValue.value.length
276
+
277
+ const selectionContainsIndex = (index: number) =>
278
+ selectionStart != null
279
+ && selectionEnd != null
280
+ && index >= selectionStart
281
+ && index < selectionEnd
282
+
283
+ if (
284
+ ANY_MINUS_DETECT_RE.test(event.key)
285
+ && Array.from(allowedNonNumericKeys).some(k => ANY_MINUS_DETECT_RE.test(k || ''))
286
+ ) {
287
+ // Only allow one sign unless replacing the existing one or all text is selected
288
+ const existingIndex = inputValue.value.search(ANY_MINUS_RE)
289
+ const isReplacingExisting
290
+ = existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex)
291
+ isAllowedNonNumericKey
292
+ = !(ANY_MINUS_DETECT_RE.test(inputValue.value) || ANY_PLUS_DETECT_RE.test(inputValue.value))
293
+ || isAllSelected
294
+ || isReplacingExisting
295
+ }
296
+ if (
297
+ ANY_PLUS_DETECT_RE.test(event.key)
298
+ && Array.from(allowedNonNumericKeys).some(k => ANY_PLUS_DETECT_RE.test(k || ''))
299
+ ) {
300
+ const existingIndex = inputValue.value.search(ANY_PLUS_RE)
301
+ const isReplacingExisting
302
+ = existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex)
303
+ isAllowedNonNumericKey
304
+ = !(ANY_MINUS_DETECT_RE.test(inputValue.value) || ANY_PLUS_DETECT_RE.test(inputValue.value))
305
+ || isAllSelected
306
+ || isReplacingExisting
307
+ }
308
+
309
+ // Only allow one of each symbol.
310
+ ;[decimal, currency, percentSign].forEach((symbol) => {
311
+ if (event.key === symbol && symbol) {
312
+ const symbolIndex = inputValue.value.indexOf(symbol)
313
+ const isSymbolHighlighted = selectionContainsIndex(symbolIndex)
314
+ isAllowedNonNumericKey
315
+ = !inputValue.value.includes(symbol) || isAllSelected || isSymbolHighlighted
316
+ }
317
+ })
318
+
319
+ const isAsciiDigit = event.key >= '0' && event.key <= '9'
320
+ const isArabicNumeral = ARABIC_DETECT_RE.test(event.key)
321
+ const isHanNumeral = HAN_DETECT_RE.test(event.key)
322
+ const isPersianNumeral = PERSIAN_DETECT_RE.test(event.key)
323
+ const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(event.key)
324
+ const isNavigateKey = NAVIGATE_KEYS.has(event.key)
325
+ // Alt+ArrowUp/ArrowDown selects smallStep, so don't treat it as a bypass modifier.
326
+ const isStepKey = event.key === 'ArrowUp' || event.key === 'ArrowDown'
327
+
328
+ if (
329
+ // Allow composition events (e.g., pinyin)
330
+ (event as any).which === 229
331
+ || (event.altKey && !isStepKey)
332
+ || event.ctrlKey
333
+ || event.metaKey
334
+ || isAllowedNonNumericKey
335
+ || isAsciiDigit
336
+ || isArabicNumeral
337
+ || isFullwidthNumeral
338
+ || isHanNumeral
339
+ || isPersianNumeral
340
+ || isNavigateKey
341
+ ) {
342
+ return
343
+ }
344
+
345
+ // We need to commit the number at this point if the input hasn't been blurred.
346
+ const parsedValue = parseNumber(inputValue.value, locale.value, formatOptionsRef.value)
347
+
348
+ const amount = getStepAmount(event) ?? DEFAULT_STEP
349
+
350
+ // Prevent insertion of text or caret from moving.
351
+ stopEvent(event)
352
+
353
+ const commitDetails = createGenericEventDetails(REASONS.keyboard, event)
354
+
355
+ if (event.key === 'ArrowUp') {
356
+ incrementValue(amount, {
357
+ direction: 1,
358
+ currentValue: parsedValue,
359
+ event,
360
+ reason: REASONS.keyboard,
361
+ })
362
+ onValueCommitted(lastChangedValueRef.value ?? valueRef.value, commitDetails)
363
+ }
364
+ else if (event.key === 'ArrowDown') {
365
+ incrementValue(amount, {
366
+ direction: -1,
367
+ currentValue: parsedValue,
368
+ event,
369
+ reason: REASONS.keyboard,
370
+ })
371
+ onValueCommitted(lastChangedValueRef.value ?? valueRef.value, commitDetails)
372
+ }
373
+ else if (event.key === 'Home' && min.value != null) {
374
+ setValue(min.value, createChangeEventDetails(REASONS.keyboard, event))
375
+ onValueCommitted(lastChangedValueRef.value ?? valueRef.value, commitDetails)
376
+ }
377
+ else if (event.key === 'End' && max.value != null) {
378
+ setValue(max.value, createChangeEventDetails(REASONS.keyboard, event))
379
+ onValueCommitted(lastChangedValueRef.value ?? valueRef.value, commitDetails)
380
+ }
381
+ }
382
+
383
+ function onPaste(event: ClipboardEvent) {
384
+ if (event.defaultPrevented || readOnly.value || disabled.value) {
385
+ return
386
+ }
387
+
388
+ let pastedData = ''
389
+
390
+ try {
391
+ pastedData = event.clipboardData?.getData('text/plain') ?? ''
392
+ }
393
+ catch {
394
+ if (process.env.NODE_ENV !== 'production') {
395
+ warn('<NumberFieldInput> could not read clipboard text during paste handling.')
396
+ }
397
+ return
398
+ }
399
+
400
+ // Prevent `onInput` from being called.
401
+ event.preventDefault()
402
+
403
+ const parsedValue = parseNumber(pastedData, locale.value, formatOptionsRef.value)
404
+
405
+ if (parsedValue !== null) {
406
+ allowInputSyncRef.value = false
407
+ setValue(parsedValue, createChangeEventDetails(REASONS.inputPaste, event))
408
+ setInputValue(pastedData)
409
+ }
410
+ }
411
+
412
+ const inputProps = computed(() => mergeProps(
413
+ attrsObject,
414
+ validation.getValidationProps(),
415
+ {
416
+ 'id': id.value,
417
+ 'required': required.value,
418
+ 'disabled': disabled.value,
419
+ 'readonly': readOnly.value,
420
+ 'inputmode': inputMode.value,
421
+ 'value': inputValue.value,
422
+ 'type': 'text',
423
+ 'autocomplete': 'off',
424
+ 'autocorrect': 'off',
425
+ 'spellcheck': 'false',
426
+ 'aria-roledescription': 'Number field',
427
+ 'aria-invalid': !disabled.value && invalid.value ? true : undefined,
428
+ 'aria-labelledby': labelId.value,
429
+ 'onFocus': onFocus,
430
+ 'onBlur': onBlur,
431
+ 'onInput': onInput,
432
+ 'onKeydown': onKeydown,
433
+ 'onPaste': onPaste,
434
+ },
435
+ ))
436
+
437
+ const {
438
+ tag,
439
+ mergedProps,
440
+ renderless,
441
+ ref: renderRef,
442
+ } = useRenderElement({
443
+ componentProps: props,
444
+ state,
445
+ props: inputProps,
446
+ stateAttributesMapping,
447
+ defaultTagName: 'input',
448
+ ref: setInputRef,
449
+ })
450
+ </script>
451
+
452
+ <template>
453
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
454
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps" />
455
+ </template>