@saasmakers/ui 2.1.1 → 2.1.3

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,7 +46,13 @@ 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 } = useDevice()
49
+ const { isDesktopBrowser, isIos } = useDevice()
50
+
51
+ const iosUppercaseTyping = computed(() => {
52
+ const isProseField = props.type === 'text' || props.type === 'search'
53
+
54
+ return props.uppercase && isIos.value && isProseField && !props.lowercaseOnly && !props.slugOnly
55
+ })
50
56
 
51
57
  onMounted(() => {
52
58
  if (props.autofocus && input.value && isDesktopBrowser.value) {
@@ -77,6 +83,14 @@ function onFieldBlur(event: FocusEvent) {
77
83
  }
78
84
 
79
85
  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
+
80
94
  emit('focus', event)
81
95
  }
82
96
 
@@ -193,10 +207,12 @@ defineExpose({
193
207
  :id="id"
194
208
  ref="input"
195
209
  :autocomplete="autocomplete ? 'on' : 'off'"
210
+ :autocapitalize="iosUppercaseTyping ? 'characters' : undefined"
196
211
  v-bind="autofocus ? { 'data-autofocus': '' } : {}"
197
212
  class="h-full w-full appearance-none items-center rounded-lg font-medium tracking-tight outline-none placeholder-gray-600 dark:placeholder-gray-400"
198
213
  :class="{
199
- 'uppercase': uppercase,
214
+ 'uppercase': uppercase && !iosUppercaseTyping,
215
+ 'placeholder:uppercase': iosUppercaseTyping,
200
216
  'field-disabled': disabled,
201
217
  'focus:placeholder-gray-900 hover:placeholder-gray-900 dark:focus:placeholder-gray-100 dark:hover:placeholder-gray-100': !disabled,
202
218
  'text-gray-900 dark:text-gray-100': !lineThrough,
@@ -26,6 +26,7 @@ const props = withDefaults(defineProps<Omit<FieldSelect, 'modelValue'>>(), {
26
26
  searchPlaceholder: '',
27
27
  size: 'base',
28
28
  square: false,
29
+ uppercase: true,
29
30
  validation: undefined,
30
31
  })
31
32
 
@@ -331,6 +332,7 @@ function toggle() {
331
332
  'cursor-pointer': !disabled,
332
333
  'shadow-sm': border === 'full',
333
334
  'w-fit': iconOnly || square,
335
+ 'field-uppercase': uppercase,
334
336
  }"
335
337
  >
336
338
  <div
@@ -445,6 +447,7 @@ function toggle() {
445
447
  'text-xs': size === 'sm',
446
448
  'text-sm': size === 'base',
447
449
  'text-base': size === 'lg',
450
+ 'field-uppercase': uppercase,
448
451
  'md:min-w-xl p-1 pb-0': isMultiColumn,
449
452
  }"
450
453
  role="listbox"
@@ -30,7 +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 } = useDevice()
33
+ const { isDesktopBrowser, isIos } = useDevice()
34
+
35
+ const iosUppercaseTyping = computed(() => {
36
+ return props.uppercase && isIos.value
37
+ })
34
38
 
35
39
  onMounted(() => {
36
40
  if (props.autofocus && textarea.value && isDesktopBrowser.value) {
@@ -47,6 +51,18 @@ function onFieldClick(event: MouseEvent) {
47
51
  emit('click', event, modelValue.value || '')
48
52
  }
49
53
 
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
+
50
66
  function syncModelValueFromTarget(target: EventTarget | null) {
51
67
  if (!(target instanceof HTMLTextAreaElement)) {
52
68
  return
@@ -77,9 +93,11 @@ function syncModelValueFromTarget(target: EventTarget | null) {
77
93
  ref="textarea"
78
94
  v-model="modelValue"
79
95
  v-bind="{ 'data-enable-grammarly': 'false' }"
96
+ :autocapitalize="iosUppercaseTyping ? 'characters' : undefined"
80
97
  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"
81
98
  :class="{
82
- 'uppercase': uppercase,
99
+ 'uppercase': uppercase && !iosUppercaseTyping,
100
+ 'placeholder:uppercase': iosUppercaseTyping,
83
101
  'field-disabled': disabled,
84
102
  'focus:placeholder-gray-900 hover:placeholder-gray-900 dark:focus:placeholder-gray-100 dark:hover:placeholder-gray-100': !disabled,
85
103
  'px-sm py-2.5': padding && size === 'sm',
@@ -102,6 +120,7 @@ function syncModelValueFromTarget(target: EventTarget | null) {
102
120
  spellcheck="false"
103
121
  @blur="onFieldBlur"
104
122
  @click="onFieldClick"
123
+ @focus="onFieldFocus"
105
124
  />
106
125
 
107
126
  <FieldMessage
@@ -1,5 +1,7 @@
1
1
  export default function useDevice() {
2
- const { isNative } = useCapacitor()
2
+ const { isIOS, isNative } = useCapacitor()
3
+
4
+ const isIos = ref(false)
3
5
 
4
6
  const isDesktopBrowser = computed(() => {
5
7
  if (typeof navigator === 'undefined') {
@@ -18,5 +20,29 @@ export default function useDevice() {
18
20
  return !/Android|iPad|iPhone|iPod|Mobile/i.test(navigator.userAgent)
19
21
  })
20
22
 
21
- return { isDesktopBrowser }
23
+ onMounted(() => {
24
+ isIos.value = detectIosDevice(isIOS.value)
25
+ })
26
+
27
+ return {
28
+ isDesktopBrowser,
29
+ isIos,
30
+ }
31
+ }
32
+
33
+ function detectIosDevice(isNativeIos: boolean) {
34
+ if (isNativeIos) {
35
+ return true
36
+ }
37
+
38
+ if (typeof navigator === 'undefined') {
39
+ return false
40
+ }
41
+
42
+ if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
43
+ return true
44
+ }
45
+
46
+ // iPadOS 13+ uses a Macintosh user agent. Several touch points separate it from a Mac.
47
+ return navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1
22
48
  }
@@ -179,6 +179,7 @@ export interface FieldSelect {
179
179
  searchPlaceholder?: string
180
180
  size?: FieldSize
181
181
  square?: boolean
182
+ uppercase?: boolean
182
183
  validation?: VuelidateValidation
183
184
  }
184
185
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/ui",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "private": false,
5
5
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
6
6
  "license": "MIT",