@saasmakers/ui 2.1.5 → 2.1.6

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.
@@ -48,7 +48,7 @@ const input = ref<HTMLInputElement>()
48
48
  const showPassword = ref(false)
49
49
  const { isDesktopBrowser } = useDevice()
50
50
 
51
- const { isTypingOnIos, onBlur: onIosUppercaseBlur, onFocus: onIosUppercaseFocus, usesIosUppercaseKeyboard } = useIosUppercase({
51
+ const { isActiveOnIos, isTypingOnIos, onBlur: onIosUppercaseBlur, onCompositionEnd: onIosUppercaseCompositionEnd, onFocus: onIosUppercaseFocus, onInput: onIosUppercaseInput } = useIosUppercase({
52
52
  enabled: () => {
53
53
  const isProseField = props.type === 'text' || props.type === 'search'
54
54
 
@@ -92,6 +92,7 @@ function onFieldFocus(event: FocusEvent) {
92
92
  }
93
93
 
94
94
  function onFieldInput(event: Event) {
95
+ onIosUppercaseInput(event)
95
96
  syncModelValueFromTarget(event.target)
96
97
  }
97
98
 
@@ -204,7 +205,7 @@ defineExpose({
204
205
  :id="id"
205
206
  ref="input"
206
207
  :autocomplete="autocomplete ? 'on' : 'off'"
207
- :autocapitalize="usesIosUppercaseKeyboard ? 'characters' : undefined"
208
+ :autocapitalize="isActiveOnIos ? 'characters' : undefined"
208
209
  v-bind="autofocus ? { 'data-autofocus': '' } : {}"
209
210
  class="h-full w-full appearance-none items-center rounded-lg font-medium tracking-tight outline-none placeholder-gray-600 dark:placeholder-gray-400"
210
211
  :class="{
@@ -244,6 +245,7 @@ defineExpose({
244
245
  :type="type === 'password' && showPassword ? 'text' : type"
245
246
  :value="modelValue"
246
247
  @blur="onFieldBlur"
248
+ @compositionend="onIosUppercaseCompositionEnd"
247
249
  @focus="onFieldFocus"
248
250
  @input="onFieldInput"
249
251
  @keydown="onFieldKeyDown"
@@ -32,7 +32,7 @@ const modelValue = defineModel<FieldTextarea['modelValue']>({ default: '' })
32
32
  const textarea = ref<HTMLTextAreaElement>()
33
33
  const { isDesktopBrowser } = useDevice()
34
34
 
35
- const { isTypingOnIos, onBlur: onIosUppercaseBlur, onFocus: onIosUppercaseFocus, usesIosUppercaseKeyboard } = useIosUppercase({
35
+ const { isActiveOnIos, isTypingOnIos, onBlur: onIosUppercaseBlur, onCompositionEnd: onIosUppercaseCompositionEnd, onFocus: onIosUppercaseFocus, onInput: onIosUppercaseInput } = useIosUppercase({
36
36
  enabled: () => props.uppercase,
37
37
  modelValue,
38
38
  })
@@ -83,7 +83,7 @@ function syncModelValueFromTarget(target: EventTarget | null) {
83
83
  ref="textarea"
84
84
  v-model="modelValue"
85
85
  v-bind="{ 'data-enable-grammarly': 'false' }"
86
- :autocapitalize="usesIosUppercaseKeyboard ? 'characters' : undefined"
86
+ :autocapitalize="isActiveOnIos ? 'characters' : undefined"
87
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"
88
88
  :class="{
89
89
  'uppercase': uppercase && !isTypingOnIos,
@@ -110,7 +110,9 @@ function syncModelValueFromTarget(target: EventTarget | null) {
110
110
  spellcheck="false"
111
111
  @blur="onFieldBlur"
112
112
  @click="onFieldClick"
113
+ @compositionend="onIosUppercaseCompositionEnd"
113
114
  @focus="onIosUppercaseFocus"
115
+ @input="onIosUppercaseInput"
114
116
  />
115
117
 
116
118
  <FieldMessage
@@ -5,32 +5,71 @@ export default function useIosUppercase(options: {
5
5
  modelValue: Ref<string>
6
6
  }) {
7
7
  const { isIosDevice } = useDevice()
8
- const focused = ref(false)
8
+ const isFocused = ref(false)
9
9
 
10
- const usesIosUppercaseKeyboard = computed(() => {
10
+ const isActiveOnIos = computed(() => {
11
11
  return toValue(options.enabled) && isIosDevice.value
12
12
  })
13
13
 
14
14
  const isTypingOnIos = computed(() => {
15
- return usesIosUppercaseKeyboard.value && focused.value
15
+ return isActiveOnIos.value && isFocused.value
16
16
  })
17
17
 
18
18
  function onFocus() {
19
- if (usesIosUppercaseKeyboard.value) {
19
+ if (isActiveOnIos.value) {
20
20
  options.modelValue.value = options.modelValue.value.toUpperCase()
21
21
  }
22
22
 
23
- focused.value = true
23
+ isFocused.value = true
24
24
  }
25
25
 
26
26
  function onBlur() {
27
- focused.value = false
27
+ isFocused.value = false
28
+ }
29
+
30
+ function onInput(event: Event) {
31
+ // Rewriting the value mid-composition breaks IME and accent input.
32
+ if (event instanceof InputEvent && event.isComposing) {
33
+ return
34
+ }
35
+
36
+ forceUppercase(event.target)
37
+ }
38
+
39
+ function onCompositionEnd(event: CompositionEvent) {
40
+ forceUppercase(event.target)
41
+ }
42
+
43
+ function forceUppercase(target: EventTarget | null) {
44
+ if (!isActiveOnIos.value) {
45
+ return
46
+ }
47
+
48
+ if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) {
49
+ return
50
+ }
51
+
52
+ const uppercased = target.value.toUpperCase()
53
+
54
+ if (uppercased === target.value) {
55
+ return
56
+ }
57
+
58
+ const { selectionEnd, selectionStart } = target
59
+
60
+ target.value = uppercased
61
+
62
+ target.setSelectionRange(selectionStart ?? uppercased.length, selectionEnd ?? uppercased.length)
63
+
64
+ options.modelValue.value = uppercased
28
65
  }
29
66
 
30
67
  return {
68
+ isActiveOnIos,
31
69
  isTypingOnIos,
32
70
  onBlur,
71
+ onCompositionEnd,
33
72
  onFocus,
34
- usesIosUppercaseKeyboard,
73
+ onInput,
35
74
  }
36
75
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/ui",
3
- "version": "2.1.5",
3
+ "version": "2.1.6",
4
4
  "private": false,
5
5
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
6
6
  "license": "MIT",