@saasmakers/ui 2.1.3 → 2.1.5

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.
@@ -46,12 +46,15 @@ const id = useId()
46
46
  const modelValue = defineModel<FieldInput['modelValue']>({ default: '' })
47
47
  const input = ref<HTMLInputElement>()
48
48
  const showPassword = ref(false)
49
- const { isDesktopBrowser, isIos } = useDevice()
49
+ const { isDesktopBrowser } = useDevice()
50
50
 
51
- const iosUppercaseTyping = computed(() => {
52
- const isProseField = props.type === 'text' || props.type === 'search'
51
+ const { isTypingOnIos, onBlur: onIosUppercaseBlur, onFocus: onIosUppercaseFocus, usesIosUppercaseKeyboard } = useIosUppercase({
52
+ enabled: () => {
53
+ const isProseField = props.type === 'text' || props.type === 'search'
53
54
 
54
- return props.uppercase && isIos.value && isProseField && !props.lowercaseOnly && !props.slugOnly
55
+ return props.uppercase && isProseField && !props.lowercaseOnly && !props.slugOnly
56
+ },
57
+ modelValue,
55
58
  })
56
59
 
57
60
  onMounted(() => {
@@ -73,6 +76,7 @@ function focus() {
73
76
  }
74
77
 
75
78
  function onFieldBlur(event: FocusEvent) {
79
+ onIosUppercaseBlur()
76
80
  syncModelValueFromTarget(event.target)
77
81
 
78
82
  if (props.trim) {
@@ -83,14 +87,7 @@ function onFieldBlur(event: FocusEvent) {
83
87
  }
84
88
 
85
89
  function onFieldFocus(event: FocusEvent) {
86
- if (iosUppercaseTyping.value) {
87
- const uppercased = modelValue.value.toString().toUpperCase()
88
-
89
- if (uppercased !== modelValue.value) {
90
- modelValue.value = uppercased
91
- }
92
- }
93
-
90
+ onIosUppercaseFocus()
94
91
  emit('focus', event)
95
92
  }
96
93
 
@@ -207,12 +204,12 @@ defineExpose({
207
204
  :id="id"
208
205
  ref="input"
209
206
  :autocomplete="autocomplete ? 'on' : 'off'"
210
- :autocapitalize="iosUppercaseTyping ? 'characters' : undefined"
207
+ :autocapitalize="usesIosUppercaseKeyboard ? 'characters' : undefined"
211
208
  v-bind="autofocus ? { 'data-autofocus': '' } : {}"
212
209
  class="h-full w-full appearance-none items-center rounded-lg font-medium tracking-tight outline-none placeholder-gray-600 dark:placeholder-gray-400"
213
210
  :class="{
214
- 'uppercase': uppercase && !iosUppercaseTyping,
215
- 'placeholder:uppercase': iosUppercaseTyping,
211
+ 'uppercase': uppercase && !isTypingOnIos,
212
+ 'placeholder:uppercase': isTypingOnIos,
216
213
  'field-disabled': disabled,
217
214
  'focus:placeholder-gray-900 hover:placeholder-gray-900 dark:focus:placeholder-gray-100 dark:hover:placeholder-gray-100': !disabled,
218
215
  'text-gray-900 dark:text-gray-100': !lineThrough,
@@ -30,10 +30,11 @@ const emit = defineEmits<{
30
30
  const id = useId()
31
31
  const modelValue = defineModel<FieldTextarea['modelValue']>({ default: '' })
32
32
  const textarea = ref<HTMLTextAreaElement>()
33
- const { isDesktopBrowser, isIos } = useDevice()
33
+ const { isDesktopBrowser } = useDevice()
34
34
 
35
- const iosUppercaseTyping = computed(() => {
36
- return props.uppercase && isIos.value
35
+ const { isTypingOnIos, onBlur: onIosUppercaseBlur, onFocus: onIosUppercaseFocus, usesIosUppercaseKeyboard } = useIosUppercase({
36
+ enabled: () => props.uppercase,
37
+ modelValue,
37
38
  })
38
39
 
39
40
  onMounted(() => {
@@ -43,6 +44,7 @@ onMounted(() => {
43
44
  })
44
45
 
45
46
  function onFieldBlur(event: FocusEvent) {
47
+ onIosUppercaseBlur()
46
48
  syncModelValueFromTarget(event.target)
47
49
  emit('blur', event, modelValue.value || '')
48
50
  }
@@ -51,18 +53,6 @@ function onFieldClick(event: MouseEvent) {
51
53
  emit('click', event, modelValue.value || '')
52
54
  }
53
55
 
54
- function onFieldFocus() {
55
- if (!iosUppercaseTyping.value) {
56
- return
57
- }
58
-
59
- const uppercased = modelValue.value.toString().toUpperCase()
60
-
61
- if (uppercased !== modelValue.value) {
62
- modelValue.value = uppercased
63
- }
64
- }
65
-
66
56
  function syncModelValueFromTarget(target: EventTarget | null) {
67
57
  if (!(target instanceof HTMLTextAreaElement)) {
68
58
  return
@@ -93,11 +83,11 @@ function syncModelValueFromTarget(target: EventTarget | null) {
93
83
  ref="textarea"
94
84
  v-model="modelValue"
95
85
  v-bind="{ 'data-enable-grammarly': 'false' }"
96
- :autocapitalize="iosUppercaseTyping ? 'characters' : undefined"
86
+ :autocapitalize="usesIosUppercaseKeyboard ? 'characters' : undefined"
97
87
  class="w-full flex-1 appearance-none rounded-lg text-gray-900 font-medium tracking-tight outline-none dark:text-gray-100 placeholder-gray-600 dark:placeholder-gray-400"
98
88
  :class="{
99
- 'uppercase': uppercase && !iosUppercaseTyping,
100
- 'placeholder:uppercase': iosUppercaseTyping,
89
+ 'uppercase': uppercase && !isTypingOnIos,
90
+ 'placeholder:uppercase': isTypingOnIos,
101
91
  'field-disabled': disabled,
102
92
  'focus:placeholder-gray-900 hover:placeholder-gray-900 dark:focus:placeholder-gray-100 dark:hover:placeholder-gray-100': !disabled,
103
93
  'px-sm py-2.5': padding && size === 'sm',
@@ -120,7 +110,7 @@ function syncModelValueFromTarget(target: EventTarget | null) {
120
110
  spellcheck="false"
121
111
  @blur="onFieldBlur"
122
112
  @click="onFieldClick"
123
- @focus="onFieldFocus"
113
+ @focus="onIosUppercaseFocus"
124
114
  />
125
115
 
126
116
  <FieldMessage
@@ -1,7 +1,6 @@
1
1
  export default function useDevice() {
2
2
  const { isIOS, isNative } = useCapacitor()
3
-
4
- const isIos = ref(false)
3
+ const isIosDevice = ref(false)
5
4
 
6
5
  const isDesktopBrowser = computed(() => {
7
6
  if (typeof navigator === 'undefined') {
@@ -21,24 +20,16 @@ export default function useDevice() {
21
20
  })
22
21
 
23
22
  onMounted(() => {
24
- isIos.value = detectIosDevice(isIOS.value)
23
+ isIosDevice.value = isIOS.value || isIosBrowser()
25
24
  })
26
25
 
27
26
  return {
28
27
  isDesktopBrowser,
29
- isIos,
28
+ isIosDevice,
30
29
  }
31
30
  }
32
31
 
33
- function detectIosDevice(isNativeIos: boolean) {
34
- if (isNativeIos) {
35
- return true
36
- }
37
-
38
- if (typeof navigator === 'undefined') {
39
- return false
40
- }
41
-
32
+ function isIosBrowser() {
42
33
  if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
43
34
  return true
44
35
  }
@@ -0,0 +1,36 @@
1
+ // iOS WebKit drops keyboard suggestions while `text-transform: uppercase` is active,
2
+ // so the transform is lifted during typing and the value itself is uppercased instead.
3
+ export default function useIosUppercase(options: {
4
+ enabled: MaybeRefOrGetter<boolean>
5
+ modelValue: Ref<string>
6
+ }) {
7
+ const { isIosDevice } = useDevice()
8
+ const focused = ref(false)
9
+
10
+ const usesIosUppercaseKeyboard = computed(() => {
11
+ return toValue(options.enabled) && isIosDevice.value
12
+ })
13
+
14
+ const isTypingOnIos = computed(() => {
15
+ return usesIosUppercaseKeyboard.value && focused.value
16
+ })
17
+
18
+ function onFocus() {
19
+ if (usesIosUppercaseKeyboard.value) {
20
+ options.modelValue.value = options.modelValue.value.toUpperCase()
21
+ }
22
+
23
+ focused.value = true
24
+ }
25
+
26
+ function onBlur() {
27
+ focused.value = false
28
+ }
29
+
30
+ return {
31
+ isTypingOnIos,
32
+ onBlur,
33
+ onFocus,
34
+ usesIosUppercaseKeyboard,
35
+ }
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/ui",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
4
4
  "private": false,
5
5
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
6
6
  "license": "MIT",