@saasmakers/ui 2.1.4 → 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,17 +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 focused = ref(false)
50
- const { isDesktopBrowser, isIos } = useDevice()
49
+ const { isDesktopBrowser } = useDevice()
51
50
 
52
- const iosUppercaseTyping = computed(() => {
53
- 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'
54
54
 
55
- return props.uppercase && isIos.value && isProseField && !props.lowercaseOnly && !props.slugOnly
56
- })
57
-
58
- const hideCssUppercase = computed(() => {
59
- return iosUppercaseTyping.value && focused.value
55
+ return props.uppercase && isProseField && !props.lowercaseOnly && !props.slugOnly
56
+ },
57
+ modelValue,
60
58
  })
61
59
 
62
60
  onMounted(() => {
@@ -78,8 +76,7 @@ function focus() {
78
76
  }
79
77
 
80
78
  function onFieldBlur(event: FocusEvent) {
81
- focused.value = false
82
-
79
+ onIosUppercaseBlur()
83
80
  syncModelValueFromTarget(event.target)
84
81
 
85
82
  if (props.trim) {
@@ -90,16 +87,7 @@ function onFieldBlur(event: FocusEvent) {
90
87
  }
91
88
 
92
89
  function onFieldFocus(event: FocusEvent) {
93
- if (iosUppercaseTyping.value) {
94
- const uppercased = modelValue.value.toString().toUpperCase()
95
-
96
- if (uppercased !== modelValue.value) {
97
- modelValue.value = uppercased
98
- }
99
- }
100
-
101
- focused.value = true
102
-
90
+ onIosUppercaseFocus()
103
91
  emit('focus', event)
104
92
  }
105
93
 
@@ -216,12 +204,12 @@ defineExpose({
216
204
  :id="id"
217
205
  ref="input"
218
206
  :autocomplete="autocomplete ? 'on' : 'off'"
219
- :autocapitalize="iosUppercaseTyping ? 'characters' : undefined"
207
+ :autocapitalize="usesIosUppercaseKeyboard ? 'characters' : undefined"
220
208
  v-bind="autofocus ? { 'data-autofocus': '' } : {}"
221
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"
222
210
  :class="{
223
- 'uppercase': uppercase && !hideCssUppercase,
224
- 'placeholder:uppercase': hideCssUppercase,
211
+ 'uppercase': uppercase && !isTypingOnIos,
212
+ 'placeholder:uppercase': isTypingOnIos,
225
213
  'field-disabled': disabled,
226
214
  'focus:placeholder-gray-900 hover:placeholder-gray-900 dark:focus:placeholder-gray-100 dark:hover:placeholder-gray-100': !disabled,
227
215
  'text-gray-900 dark:text-gray-100': !lineThrough,
@@ -30,15 +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 focused = ref(false)
34
- const { isDesktopBrowser, isIos } = useDevice()
33
+ const { isDesktopBrowser } = useDevice()
35
34
 
36
- const iosUppercaseTyping = computed(() => {
37
- return props.uppercase && isIos.value
38
- })
39
-
40
- const hideCssUppercase = computed(() => {
41
- return iosUppercaseTyping.value && focused.value
35
+ const { isTypingOnIos, onBlur: onIosUppercaseBlur, onFocus: onIosUppercaseFocus, usesIosUppercaseKeyboard } = useIosUppercase({
36
+ enabled: () => props.uppercase,
37
+ modelValue,
42
38
  })
43
39
 
44
40
  onMounted(() => {
@@ -48,8 +44,7 @@ onMounted(() => {
48
44
  })
49
45
 
50
46
  function onFieldBlur(event: FocusEvent) {
51
- focused.value = false
52
-
47
+ onIosUppercaseBlur()
53
48
  syncModelValueFromTarget(event.target)
54
49
  emit('blur', event, modelValue.value || '')
55
50
  }
@@ -58,18 +53,6 @@ function onFieldClick(event: MouseEvent) {
58
53
  emit('click', event, modelValue.value || '')
59
54
  }
60
55
 
61
- function onFieldFocus() {
62
- if (iosUppercaseTyping.value) {
63
- const uppercased = modelValue.value.toString().toUpperCase()
64
-
65
- if (uppercased !== modelValue.value) {
66
- modelValue.value = uppercased
67
- }
68
- }
69
-
70
- focused.value = true
71
- }
72
-
73
56
  function syncModelValueFromTarget(target: EventTarget | null) {
74
57
  if (!(target instanceof HTMLTextAreaElement)) {
75
58
  return
@@ -100,11 +83,11 @@ function syncModelValueFromTarget(target: EventTarget | null) {
100
83
  ref="textarea"
101
84
  v-model="modelValue"
102
85
  v-bind="{ 'data-enable-grammarly': 'false' }"
103
- :autocapitalize="iosUppercaseTyping ? 'characters' : undefined"
86
+ :autocapitalize="usesIosUppercaseKeyboard ? 'characters' : undefined"
104
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"
105
88
  :class="{
106
- 'uppercase': uppercase && !hideCssUppercase,
107
- 'placeholder:uppercase': hideCssUppercase,
89
+ 'uppercase': uppercase && !isTypingOnIos,
90
+ 'placeholder:uppercase': isTypingOnIos,
108
91
  'field-disabled': disabled,
109
92
  'focus:placeholder-gray-900 hover:placeholder-gray-900 dark:focus:placeholder-gray-100 dark:hover:placeholder-gray-100': !disabled,
110
93
  'px-sm py-2.5': padding && size === 'sm',
@@ -127,7 +110,7 @@ function syncModelValueFromTarget(target: EventTarget | null) {
127
110
  spellcheck="false"
128
111
  @blur="onFieldBlur"
129
112
  @click="onFieldClick"
130
- @focus="onFieldFocus"
113
+ @focus="onIosUppercaseFocus"
131
114
  />
132
115
 
133
116
  <FieldMessage
@@ -1,6 +1,6 @@
1
1
  export default function useDevice() {
2
2
  const { isIOS, isNative } = useCapacitor()
3
- const isIos = ref(false)
3
+ const isIosDevice = ref(false)
4
4
 
5
5
  const isDesktopBrowser = computed(() => {
6
6
  if (typeof navigator === 'undefined') {
@@ -20,24 +20,16 @@ export default function useDevice() {
20
20
  })
21
21
 
22
22
  onMounted(() => {
23
- isIos.value = detectIosDevice(isIOS.value)
23
+ isIosDevice.value = isIOS.value || isIosBrowser()
24
24
  })
25
25
 
26
26
  return {
27
27
  isDesktopBrowser,
28
- isIos,
28
+ isIosDevice,
29
29
  }
30
30
  }
31
31
 
32
- function detectIosDevice(isNativeIos: boolean) {
33
- if (isNativeIos) {
34
- return true
35
- }
36
-
37
- if (typeof navigator === 'undefined') {
38
- return false
39
- }
40
-
32
+ function isIosBrowser() {
41
33
  if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
42
34
  return true
43
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.4",
3
+ "version": "2.1.5",
4
4
  "private": false,
5
5
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
6
6
  "license": "MIT",