@saasmakers/ui 2.0.81 → 2.0.82
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,10 @@ 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
50
|
|
|
50
|
-
// On mobile, a prior focusWarmupInput() already opened the keyboard in the
|
|
51
|
-
// user gesture; focusing here moves focus input→input so the keyboard stays
|
|
52
|
-
// open and typing lands in the visible field.
|
|
53
51
|
onMounted(() => {
|
|
54
|
-
if (props.autofocus && input.value) {
|
|
52
|
+
if (props.autofocus && input.value && isDesktopBrowser.value) {
|
|
55
53
|
input.value.focus()
|
|
56
54
|
}
|
|
57
55
|
})
|
|
@@ -30,11 +30,10 @@ 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
34
|
|
|
34
|
-
// Same as FieldInput: on mobile, transfer focus from the keyboard warmup
|
|
35
|
-
// input so typing lands in the visible field while the keyboard stays open.
|
|
36
35
|
onMounted(() => {
|
|
37
|
-
if (props.autofocus && textarea.value) {
|
|
36
|
+
if (props.autofocus && textarea.value && isDesktopBrowser.value) {
|
|
38
37
|
textarea.value.focus()
|
|
39
38
|
}
|
|
40
39
|
})
|
|
@@ -18,8 +18,6 @@ const visible = defineModel<boolean>({ default: false })
|
|
|
18
18
|
const contentRef = ref<HTMLElement>()
|
|
19
19
|
const panelRef = ref<HTMLElement>()
|
|
20
20
|
const { release: onAfterLeave } = useDialog(visible, panelRef)
|
|
21
|
-
const { isKeyboardOpen, keyboardInset } = useKeyboardInset()
|
|
22
|
-
const skipLeaveSlide = ref(false)
|
|
23
21
|
|
|
24
22
|
const drag = reactive({
|
|
25
23
|
closing: false,
|
|
@@ -34,30 +32,6 @@ const closeThreshold = computed(() => {
|
|
|
34
32
|
return (panelRef.value?.offsetHeight ?? 320) * closeThresholdRatio
|
|
35
33
|
})
|
|
36
34
|
|
|
37
|
-
// Use `bottom` (not transform) so keyboard lift does not fight drag-to-dismiss.
|
|
38
|
-
const panelStyle = computed(() => {
|
|
39
|
-
return {
|
|
40
|
-
bottom: `${keyboardInset.value}px`,
|
|
41
|
-
maxHeight: isKeyboardOpen.value
|
|
42
|
-
? `calc(100dvh - ${keyboardInset.value}px - env(safe-area-inset-top))`
|
|
43
|
-
: undefined,
|
|
44
|
-
transform: drag.closing ? 'translateY(100%)' : drag.offset > 0 ? `translateY(${drag.offset}px)` : undefined,
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
function dismissKeyboard() {
|
|
49
|
-
if (!import.meta.client) {
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const activeElement = document.activeElement
|
|
54
|
-
|
|
55
|
-
// Blur instead of Keyboard.hide() so focus is cleared too (incl. the warmup input).
|
|
56
|
-
if (activeElement instanceof HTMLElement && activeElement !== document.body) {
|
|
57
|
-
activeElement.blur()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
35
|
function onBackdropClick() {
|
|
62
36
|
if (props.closeOnBackdrop) {
|
|
63
37
|
onClose()
|
|
@@ -98,12 +72,6 @@ function onHeaderPointerDown(event: PointerEvent) {
|
|
|
98
72
|
handle.setPointerCapture(event.pointerId)
|
|
99
73
|
}
|
|
100
74
|
|
|
101
|
-
function onLeaveComplete() {
|
|
102
|
-
skipLeaveSlide.value = false
|
|
103
|
-
|
|
104
|
-
onAfterLeave()
|
|
105
|
-
}
|
|
106
|
-
|
|
107
75
|
function onPanelTransitionEnd(event: TransitionEvent) {
|
|
108
76
|
if (event.propertyName !== 'transform' || !drag.closing) {
|
|
109
77
|
return
|
|
@@ -188,13 +156,6 @@ function onPointerUp(event: PointerEvent) {
|
|
|
188
156
|
handle.releasePointerCapture(event.pointerId)
|
|
189
157
|
|
|
190
158
|
if (drag.offset >= closeThreshold.value) {
|
|
191
|
-
if (isKeyboardOpen.value) {
|
|
192
|
-
onClose()
|
|
193
|
-
resetDragState()
|
|
194
|
-
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
|
|
198
159
|
drag.closing = true
|
|
199
160
|
|
|
200
161
|
return
|
|
@@ -219,10 +180,6 @@ function resetPendingDrag() {
|
|
|
219
180
|
|
|
220
181
|
watch(visible, (isVisible) => {
|
|
221
182
|
if (!isVisible) {
|
|
222
|
-
// iOS hides the keyboard before a leave slide would finish.
|
|
223
|
-
skipLeaveSlide.value = isKeyboardOpen.value
|
|
224
|
-
|
|
225
|
-
dismissKeyboard()
|
|
226
183
|
resetDragState()
|
|
227
184
|
}
|
|
228
185
|
})
|
|
@@ -240,28 +197,28 @@ watch(visible, (isVisible) => {
|
|
|
240
197
|
<ClientOnly>
|
|
241
198
|
<Teleport to="body">
|
|
242
199
|
<Transition
|
|
243
|
-
:css="!skipLeaveSlide"
|
|
244
200
|
enter-active-class="transition-transform duration-300 ease motion-reduce:transition-none motion-reduce:duration-0"
|
|
245
201
|
enter-from-class="translate-y-full motion-reduce:translate-y-0"
|
|
246
202
|
enter-to-class="translate-y-0"
|
|
247
203
|
leave-active-class="transition-transform duration-300 ease motion-reduce:transition-none motion-reduce:duration-0"
|
|
248
204
|
leave-from-class="translate-y-0"
|
|
249
205
|
leave-to-class="translate-y-full motion-reduce:translate-y-0"
|
|
250
|
-
@after-leave="
|
|
206
|
+
@after-leave="onAfterLeave"
|
|
251
207
|
>
|
|
252
208
|
<div
|
|
253
209
|
v-if="visible"
|
|
254
210
|
ref="panelRef"
|
|
255
211
|
:aria-label="props['aria-label']"
|
|
256
212
|
aria-modal="true"
|
|
257
|
-
class="layout-bottom-sheet fixed bottom-0 left-0 right-0 z-[60] mx-auto max-h-[85dvh] max-w-screen-md flex flex-col rounded-t-2xl bg-white text-gray-900 app-text shadow-lg outline-none dark:bg-gray-900 dark:text-gray-100"
|
|
213
|
+
class="layout-bottom-sheet fixed bottom-0 left-0 right-0 z-[60] mx-auto max-h-[85dvh] max-w-screen-md flex flex-col rounded-t-2xl bg-white text-gray-900 app-text shadow-lg outline-none safe-bottom dark:bg-gray-900 dark:text-gray-100"
|
|
258
214
|
:class="{
|
|
259
|
-
'safe-bottom': !isKeyboardOpen,
|
|
260
215
|
'transition-none motion-reduce:transition-none': drag.isDragging,
|
|
261
|
-
'transition-
|
|
216
|
+
'transition-transform duration-200 ease motion-reduce:transition-none motion-reduce:duration-0': !drag.isDragging && (drag.closing || drag.offset > 0),
|
|
262
217
|
}"
|
|
263
|
-
:style="panelStyle"
|
|
264
218
|
role="dialog"
|
|
219
|
+
:style="{
|
|
220
|
+
transform: drag.closing ? 'translateY(100%)' : drag.offset > 0 ? `translateY(${drag.offset}px)` : undefined,
|
|
221
|
+
}"
|
|
265
222
|
tabindex="-1"
|
|
266
223
|
@transitionend="onPanelTransitionEnd"
|
|
267
224
|
>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saasmakers/ui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.82",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Reusable Nuxt UI components for SaaS Makers projects",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@capacitor/core": "8.4.0",
|
|
29
|
-
"@capacitor/keyboard": "8.0.6",
|
|
30
29
|
"@capacitor/preferences": "8.0.1",
|
|
31
30
|
"@capgo/capacitor-social-login": "8.3.22",
|
|
32
31
|
"@nuxt/icon": "2.2.3",
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { PluginListenerHandle } from '@capacitor/core'
|
|
2
|
-
import { Keyboard } from '@capacitor/keyboard'
|
|
3
|
-
|
|
4
|
-
// Tracks the on-screen keyboard height on iOS Capacitor only.
|
|
5
|
-
// With Keyboard.resize = 'none', the WebView does not shrink; consumers shift
|
|
6
|
-
// fixed UI (e.g. bottom sheets) with this inset so it moves with the keyboard
|
|
7
|
-
// animation instead of jumping after it finishes. Android keeps soft-input
|
|
8
|
-
// resize, so applying the inset there would double-offset.
|
|
9
|
-
export default function useKeyboardInset() {
|
|
10
|
-
const { isIOS, isPluginAvailable } = useCapacitor()
|
|
11
|
-
const keyboardInset = ref(0)
|
|
12
|
-
|
|
13
|
-
const isKeyboardOpen = computed(() => {
|
|
14
|
-
return keyboardInset.value > 0
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const listenerHandles: PluginListenerHandle[] = []
|
|
18
|
-
|
|
19
|
-
onMounted(async () => {
|
|
20
|
-
if (!import.meta.client || !isIOS.value || !isPluginAvailable('Keyboard')) {
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
listenerHandles.push(
|
|
25
|
-
await Keyboard.addListener('keyboardWillShow', (info) => {
|
|
26
|
-
keyboardInset.value = info.keyboardHeight
|
|
27
|
-
}),
|
|
28
|
-
await Keyboard.addListener('keyboardWillHide', () => {
|
|
29
|
-
keyboardInset.value = 0
|
|
30
|
-
}),
|
|
31
|
-
)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
onBeforeUnmount(() => {
|
|
35
|
-
for (const handle of listenerHandles) {
|
|
36
|
-
void handle.remove()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
listenerHandles.length = 0
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
isKeyboardOpen,
|
|
44
|
-
keyboardInset,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// Always-mounted hidden input used to open the mobile keyboard within the user
|
|
2
|
-
// gesture, before any async work (network create) breaks user activation.
|
|
3
|
-
// Kept at module scope so every consumer shares the same singleton input.
|
|
4
|
-
let warmupInput: HTMLInputElement | undefined
|
|
5
|
-
|
|
6
|
-
export default function useKeyboardWarmup() {
|
|
7
|
-
const { isDesktopBrowser } = useDevice()
|
|
8
|
-
|
|
9
|
-
function ensureWarmupInput() {
|
|
10
|
-
if (!import.meta.client || warmupInput) {
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const input = document.createElement('input')
|
|
15
|
-
|
|
16
|
-
input.type = 'text'
|
|
17
|
-
input.tabIndex = -1
|
|
18
|
-
|
|
19
|
-
input.setAttribute('aria-hidden', 'true')
|
|
20
|
-
|
|
21
|
-
input.style.cssText = 'position:fixed;top:0;left:0;height:1px;width:1px;opacity:0;pointer-events:none;'
|
|
22
|
-
|
|
23
|
-
document.body.appendChild(input)
|
|
24
|
-
|
|
25
|
-
warmupInput = input
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Must run synchronously inside a user gesture. Focusing a real text input
|
|
29
|
-
// raises the on-screen keyboard; a later text-to-text focus move (to the
|
|
30
|
-
// profile name field) keeps it open without needing a fresh gesture.
|
|
31
|
-
function focusWarmupInput() {
|
|
32
|
-
if (isDesktopBrowser.value) {
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
ensureWarmupInput()
|
|
37
|
-
warmupInput?.focus()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
ensureWarmupInput,
|
|
42
|
-
focusWarmupInput,
|
|
43
|
-
}
|
|
44
|
-
}
|