@bagelink/vue 1.4.122 → 1.4.128
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.
- package/dist/components/form/inputs/SelectInput.vue.d.ts +2 -0
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/composables/useDevice.d.ts +2 -0
- package/dist/composables/useDevice.d.ts.map +1 -1
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/directives/pattern.d.ts.map +1 -1
- package/dist/index.cjs +10 -10
- package/dist/index.mjs +8 -8
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/form/inputs/SelectInput.vue +11 -2
- package/src/components/layout/AppSidebar.vue +3 -3
- package/src/composables/useDevice.ts +28 -0
- package/src/composables/useSchemaField.ts +10 -1
- package/src/directives/pattern.ts +122 -47
package/package.json
CHANGED
|
@@ -36,6 +36,7 @@ interface PropTypes {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const searchInput = $ref<HTMLElement | undefined>()
|
|
39
|
+
const triggerBtn = $ref<HTMLButtonElement | undefined>()
|
|
39
40
|
|
|
40
41
|
let selectedItems = $ref<Option[]>([])
|
|
41
42
|
|
|
@@ -145,7 +146,15 @@ function select(option: Option) {
|
|
|
145
146
|
selectedItems.splice(0, selectedItemCount, option)
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
// Move focus away from popper content before it gets aria-hidden
|
|
150
|
+
const active = document.activeElement as HTMLElement | null
|
|
151
|
+
if (active && selectOptions?.contains(active)) active.blur()
|
|
152
|
+
|
|
153
|
+
if (!props.multiselect) {
|
|
154
|
+
open = false
|
|
155
|
+
// After closing, return focus to the trigger for accessibility
|
|
156
|
+
setTimeout(() => { triggerBtn?.focus() }, 0)
|
|
157
|
+
}
|
|
149
158
|
emitUpdate()
|
|
150
159
|
}
|
|
151
160
|
|
|
@@ -241,7 +250,7 @@ onMounted(() => {
|
|
|
241
250
|
@keydown.down.prevent="navigate('down')" @keydown.up.prevent="navigate('up')"
|
|
242
251
|
/>
|
|
243
252
|
<button
|
|
244
|
-
v-else :disabled="disabled" type="button" class="selectinput-btn"
|
|
253
|
+
v-else ref="triggerBtn" :disabled="disabled" type="button" class="selectinput-btn"
|
|
245
254
|
:class="{ isEmpty: selectedItemCount === 0 }" @keydown.down.prevent="navigate('down')"
|
|
246
255
|
@keydown.up.prevent="navigate('up')"
|
|
247
256
|
>
|
|
@@ -179,7 +179,7 @@ function logout() {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
.app-sidebar {
|
|
182
|
-
transition: width 0.
|
|
182
|
+
transition: width 0.4s ease;
|
|
183
183
|
--input-font-size: 0.875rem;
|
|
184
184
|
/* 14px */
|
|
185
185
|
}
|
|
@@ -227,7 +227,7 @@ function logout() {
|
|
|
227
227
|
/* אנימציות נכנסות ויוצאות */
|
|
228
228
|
@media (min-width: 911px) {
|
|
229
229
|
.app-sidebar {
|
|
230
|
-
transition: width 0.
|
|
230
|
+
transition: width 0.4s ease;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/* כשהמניו סגור - הטקסט נעלם תחילה */
|
|
@@ -257,7 +257,7 @@ function logout() {
|
|
|
257
257
|
@media (max-width: 910px) {
|
|
258
258
|
.app-sidebar {
|
|
259
259
|
transform: translateX(-100%);
|
|
260
|
-
transition: transform 0.
|
|
260
|
+
transition: transform 0.4s ease;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
.sidebar-mobile-open {
|
|
@@ -7,12 +7,38 @@ export function useDevice() {
|
|
|
7
7
|
const scrollX = ref(window.scrollX)
|
|
8
8
|
const innerHeight = ref(window.innerHeight)
|
|
9
9
|
|
|
10
|
+
// Scroll direction tracking
|
|
11
|
+
const scrollDirY = ref<'up' | 'down' | null>(null)
|
|
12
|
+
const scrollDirX = ref<'left' | 'right' | null>(null)
|
|
13
|
+
const prevScrollY = ref(window.scrollY)
|
|
14
|
+
const prevScrollX = ref(window.scrollX)
|
|
15
|
+
|
|
10
16
|
function updateDeviceInfo() {
|
|
17
|
+
// Store previous scroll positions
|
|
18
|
+
prevScrollY.value = scrollY.value
|
|
19
|
+
prevScrollX.value = scrollX.value
|
|
20
|
+
|
|
21
|
+
// Update current values
|
|
11
22
|
innerWidth.value = window.innerWidth
|
|
12
23
|
isMobile.value = window.innerWidth < 768
|
|
13
24
|
scrollY.value = window.scrollY
|
|
14
25
|
scrollX.value = window.scrollX
|
|
15
26
|
innerHeight.value = window.innerHeight
|
|
27
|
+
|
|
28
|
+
// Determine scroll directions with threshold
|
|
29
|
+
const scrollThreshold = 10
|
|
30
|
+
|
|
31
|
+
if (scrollY.value > prevScrollY.value + scrollThreshold) {
|
|
32
|
+
scrollDirY.value = 'down'
|
|
33
|
+
} else if (scrollY.value < prevScrollY.value - scrollThreshold) {
|
|
34
|
+
scrollDirY.value = 'up'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (scrollX.value > prevScrollX.value + scrollThreshold) {
|
|
38
|
+
scrollDirX.value = 'right'
|
|
39
|
+
} else if (scrollX.value < prevScrollX.value - scrollThreshold) {
|
|
40
|
+
scrollDirX.value = 'left'
|
|
41
|
+
}
|
|
16
42
|
}
|
|
17
43
|
|
|
18
44
|
onMounted(() => {
|
|
@@ -32,5 +58,7 @@ export function useDevice() {
|
|
|
32
58
|
scrollY,
|
|
33
59
|
scrollX,
|
|
34
60
|
innerHeight,
|
|
61
|
+
dirY: scrollDirY,
|
|
62
|
+
dirX: scrollDirX,
|
|
35
63
|
}
|
|
36
64
|
}
|
|
@@ -235,11 +235,20 @@ export function useSchemaField<T extends { [key: string]: any }>(optns: UseSchem
|
|
|
235
235
|
props.options = (query: string) => {
|
|
236
236
|
const row = getFormData?.()
|
|
237
237
|
const val = currentValue
|
|
238
|
+
// If author expects (query) only, call directly
|
|
239
|
+
if (typeof fn === 'function' && fn.length === 1) {
|
|
240
|
+
return fn(query)
|
|
241
|
+
}
|
|
242
|
+
// If author expects (query, val, row), pass it directly
|
|
243
|
+
if (typeof fn === 'function' && fn.length >= 3) {
|
|
244
|
+
return fn(query, val, row)
|
|
245
|
+
}
|
|
246
|
+
// Otherwise evaluate (val,row) → array | (query)=>Promise | Promise
|
|
238
247
|
const out = fn(val, row)
|
|
239
248
|
if (Array.isArray(out)) return out
|
|
240
249
|
if (typeof out === 'function') return out(query)
|
|
241
250
|
if (out && typeof out.then === 'function') return out
|
|
242
|
-
return
|
|
251
|
+
return []
|
|
243
252
|
}
|
|
244
253
|
} else {
|
|
245
254
|
// Non-search components (e.g., RadioGroup): return a zero-arg async loader using latest row
|
|
@@ -20,6 +20,7 @@ const DEFAULT_PATTERNS: Record<string, PatternValue> = {
|
|
|
20
20
|
lower: { pattern: /[a-z]/i, transform: (v: string) => v.toLowerCase() },
|
|
21
21
|
upper: { pattern: /[A-Z]/i, transform: (v: string) => v.toUpperCase() },
|
|
22
22
|
slug: { pattern: /[a-z0-9-]/i, transform: (v: string) => v.toLowerCase().replace(/\s/g, '-') },
|
|
23
|
+
snake: { pattern: /[a-z_]/, transform: (v: string) => v.toLowerCase().replace(/\s/g, '_') },
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
interface PatternState {
|
|
@@ -27,7 +28,11 @@ interface PatternState {
|
|
|
27
28
|
pattern?: RegExp
|
|
28
29
|
modifier?: string
|
|
29
30
|
keydownEvent?: EventListenerOrEventListenerObject
|
|
30
|
-
|
|
31
|
+
inputEvent?: EventListenerOrEventListenerObject
|
|
32
|
+
compositionStartEvent?: EventListenerOrEventListenerObject
|
|
33
|
+
compositionEndEvent?: EventListenerOrEventListenerObject
|
|
34
|
+
composing?: boolean
|
|
35
|
+
reformatting?: boolean
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
const stateMap = new WeakMap<HTMLElement, PatternState>()
|
|
@@ -41,85 +46,155 @@ function getTarget(el: HTMLElement): HTMLInputElement | HTMLTextAreaElement | nu
|
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
function getModifiers(binding: DirectiveBinding): string {
|
|
44
|
-
const modifiers = Object.keys(binding.modifiers
|
|
45
|
-
return modifiers.length ? modifiers[modifiers.length - 1] : ''
|
|
49
|
+
const modifiers = Object.keys(binding.modifiers)
|
|
50
|
+
return modifiers.length > 0 ? modifiers[modifiers.length - 1] : ''
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
function getRegex(state: PatternState): RegExp {
|
|
49
|
-
if (state.pattern) return state.pattern
|
|
50
|
-
if (state.modifier
|
|
54
|
+
if (state.pattern != null) return state.pattern
|
|
55
|
+
if (state.modifier != null && state.modifier !== '') {
|
|
56
|
+
const presetPattern = (DEFAULT_PATTERNS as Record<string, PatternValue | undefined>)[state.modifier]?.pattern
|
|
57
|
+
return presetPattern ?? /./
|
|
58
|
+
}
|
|
51
59
|
return /./
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
function bindEvents(el: HTMLElement, target: HTMLInputElement | HTMLTextAreaElement, state: PatternState) {
|
|
55
|
-
state.keydownEvent = ((event: KeyboardEvent) => {
|
|
56
|
-
|
|
63
|
+
state.keydownEvent = ((event: KeyboardEvent) => {
|
|
64
|
+
onKeydown(event, target, state)
|
|
65
|
+
}) as EventListener
|
|
66
|
+
state.inputEvent = ((event: InputEvent) => {
|
|
67
|
+
onInput(event, target, state)
|
|
68
|
+
}) as EventListener
|
|
69
|
+
state.compositionStartEvent = (() => {
|
|
70
|
+
onCompositionStart(state)
|
|
71
|
+
}) as EventListener
|
|
72
|
+
state.compositionEndEvent = ((event: CompositionEvent) => {
|
|
73
|
+
onCompositionEnd(event, target, state)
|
|
74
|
+
}) as EventListener
|
|
57
75
|
|
|
58
76
|
target.addEventListener('keydown', state.keydownEvent)
|
|
59
|
-
target.addEventListener('
|
|
77
|
+
target.addEventListener('input', state.inputEvent, { capture: true })
|
|
78
|
+
target.addEventListener('compositionstart', state.compositionStartEvent)
|
|
79
|
+
target.addEventListener('compositionend', state.compositionEndEvent)
|
|
60
80
|
}
|
|
61
81
|
|
|
62
82
|
function unbindEvents(target: HTMLInputElement | HTMLTextAreaElement, state: PatternState) {
|
|
63
83
|
if (state.keydownEvent) target.removeEventListener('keydown', state.keydownEvent)
|
|
64
|
-
if (state.
|
|
84
|
+
if (state.inputEvent) target.removeEventListener('input', state.inputEvent, { capture: true })
|
|
85
|
+
if (state.compositionStartEvent) target.removeEventListener('compositionstart', state.compositionStartEvent)
|
|
86
|
+
if (state.compositionEndEvent) target.removeEventListener('compositionend', state.compositionEndEvent)
|
|
65
87
|
|
|
66
88
|
state.keydownEvent = undefined
|
|
67
|
-
state.
|
|
89
|
+
state.inputEvent = undefined
|
|
90
|
+
state.compositionStartEvent = undefined
|
|
91
|
+
state.compositionEndEvent = undefined
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isNavigationOrControlKey(event: KeyboardEvent): boolean {
|
|
95
|
+
const allowedKeys = [
|
|
96
|
+
'Tab',
|
|
97
|
+
'Backspace',
|
|
98
|
+
'Delete',
|
|
99
|
+
'ArrowLeft',
|
|
100
|
+
'ArrowRight',
|
|
101
|
+
'ArrowUp',
|
|
102
|
+
'ArrowDown',
|
|
103
|
+
'Enter',
|
|
104
|
+
]
|
|
105
|
+
if (event.ctrlKey || event.altKey || event.metaKey) return true
|
|
106
|
+
return allowedKeys.includes(event.key)
|
|
68
107
|
}
|
|
69
108
|
|
|
70
109
|
function onKeydown(event: KeyboardEvent, target: HTMLInputElement | HTMLTextAreaElement, state: PatternState) {
|
|
71
|
-
if (
|
|
110
|
+
if (state.composing === true) return
|
|
111
|
+
if (isNavigationOrControlKey(event)) return
|
|
112
|
+
|
|
113
|
+
// If a transform is configured (e.g., slug/snake/lower/upper),
|
|
114
|
+
// do not block keystrokes; normalization happens on input.
|
|
115
|
+
if (state.modifier != null && state.modifier !== '') {
|
|
116
|
+
const transformFn = (DEFAULT_PATTERNS as Record<string, PatternValue | undefined>)[state.modifier]?.transform
|
|
117
|
+
if (typeof transformFn === 'function') return
|
|
118
|
+
}
|
|
72
119
|
|
|
73
120
|
const regex = getRegex(state)
|
|
74
121
|
const testKey = event.key
|
|
75
122
|
|
|
76
123
|
if (!regex.test(testKey)) {
|
|
77
124
|
event.preventDefault()
|
|
78
|
-
} else if (state.modifier) {
|
|
79
|
-
const { transform } = DEFAULT_PATTERNS[state.modifier]
|
|
80
|
-
if (transform) {
|
|
81
|
-
console.log('transform', transform)
|
|
82
|
-
event.preventDefault()
|
|
83
|
-
const transformedValue = transform(testKey)
|
|
84
|
-
const cursorPos = target.selectionStart ?? 0
|
|
85
|
-
const newValue = target.value.slice(0, cursorPos) + transformedValue + target.value.slice(cursorPos)
|
|
86
|
-
target.value = newValue
|
|
87
|
-
target.setSelectionRange(cursorPos + transformedValue.length, cursorPos + transformedValue.length)
|
|
88
|
-
}
|
|
89
125
|
}
|
|
90
126
|
}
|
|
91
127
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
function onCompositionStart(state: PatternState) {
|
|
129
|
+
state.composing = true
|
|
130
|
+
}
|
|
95
131
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
132
|
+
function onCompositionEnd(
|
|
133
|
+
event: CompositionEvent,
|
|
134
|
+
target: HTMLInputElement | HTMLTextAreaElement,
|
|
135
|
+
state: PatternState,
|
|
136
|
+
) {
|
|
137
|
+
state.composing = false
|
|
138
|
+
// Normalize after IME commits text
|
|
139
|
+
onInput(event as unknown as InputEvent, target, state)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function sanitizeTransformed(value: string, state: PatternState): string {
|
|
143
|
+
const regex = getRegex(state)
|
|
144
|
+
let transformed: string
|
|
145
|
+
if (state.modifier != null && state.modifier !== '') {
|
|
146
|
+
const transformFn = (DEFAULT_PATTERNS as Record<string, PatternValue | undefined>)[state.modifier]?.transform
|
|
147
|
+
transformed = transformFn ? transformFn(value) : value
|
|
148
|
+
} else {
|
|
149
|
+
transformed = value
|
|
150
|
+
}
|
|
151
|
+
let result = ''
|
|
152
|
+
for (let i = 0; i < transformed.length; i++) {
|
|
153
|
+
const ch = transformed[i]
|
|
154
|
+
if (regex.test(ch)) result += ch
|
|
108
155
|
}
|
|
156
|
+
return result
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function onInput(
|
|
160
|
+
event: InputEvent,
|
|
161
|
+
target: HTMLInputElement | HTMLTextAreaElement,
|
|
162
|
+
state: PatternState,
|
|
163
|
+
) {
|
|
164
|
+
if (state.reformatting === true) return
|
|
165
|
+
if (state.composing === true) return
|
|
166
|
+
|
|
167
|
+
const originalValue = target.value
|
|
168
|
+
const selectionEnd = target.selectionEnd ?? originalValue.length
|
|
169
|
+
|
|
170
|
+
const left = originalValue.slice(0, selectionEnd)
|
|
171
|
+
|
|
172
|
+
const sanitizedFull = sanitizeTransformed(originalValue, state)
|
|
173
|
+
if (sanitizedFull === originalValue) return
|
|
174
|
+
|
|
175
|
+
const leftSanitized = sanitizeTransformed(left, state)
|
|
176
|
+
const newCursor = leftSanitized.length
|
|
177
|
+
|
|
178
|
+
state.reformatting = true
|
|
179
|
+
target.value = sanitizedFull
|
|
180
|
+
target.setSelectionRange(newCursor, newCursor)
|
|
181
|
+
state.reformatting = false
|
|
109
182
|
}
|
|
110
183
|
|
|
184
|
+
// Paste is handled naturally via the input event normalization
|
|
185
|
+
|
|
111
186
|
const pattern = {
|
|
112
187
|
beforeMount(el: HTMLElement, binding: DirectiveBinding) {
|
|
113
188
|
const target = getTarget(el)
|
|
114
|
-
if (
|
|
189
|
+
if (target === null) return
|
|
115
190
|
|
|
116
191
|
const state: PatternState = {
|
|
117
192
|
modifier: getModifiers(binding),
|
|
118
193
|
}
|
|
119
194
|
|
|
120
|
-
if (typeof binding.value === 'object') {
|
|
121
|
-
state.pattern = binding.value.pattern
|
|
122
|
-
state.validateOnly = binding.value.validateOnly
|
|
195
|
+
if (typeof binding.value === 'object' && binding.value !== null) {
|
|
196
|
+
state.pattern = binding.value.pattern ?? /./
|
|
197
|
+
state.validateOnly = binding.value.validateOnly === true
|
|
123
198
|
}
|
|
124
199
|
|
|
125
200
|
stateMap.set(el, state)
|
|
@@ -127,18 +202,18 @@ const pattern = {
|
|
|
127
202
|
},
|
|
128
203
|
updated(el: HTMLElement, binding: DirectiveBinding) {
|
|
129
204
|
const target = getTarget(el)
|
|
130
|
-
if (
|
|
205
|
+
if (target === null) return
|
|
131
206
|
|
|
132
207
|
const state = stateMap.get(el)
|
|
133
|
-
if (
|
|
208
|
+
if (state === undefined) return
|
|
134
209
|
|
|
135
210
|
unbindEvents(target, state)
|
|
136
211
|
|
|
137
212
|
state.modifier = getModifiers(binding)
|
|
138
213
|
|
|
139
|
-
if (typeof binding.value === 'object') {
|
|
140
|
-
state.pattern = binding.value.pattern
|
|
141
|
-
state.validateOnly = binding.value.validateOnly
|
|
214
|
+
if (typeof binding.value === 'object' && binding.value !== null) {
|
|
215
|
+
state.pattern = binding.value.pattern ?? /./
|
|
216
|
+
state.validateOnly = binding.value.validateOnly === true
|
|
142
217
|
}
|
|
143
218
|
|
|
144
219
|
bindEvents(el, target, state)
|
|
@@ -147,7 +222,7 @@ const pattern = {
|
|
|
147
222
|
const target = getTarget(el)
|
|
148
223
|
const state = stateMap.get(el)
|
|
149
224
|
|
|
150
|
-
if (target && state) {
|
|
225
|
+
if (target !== null && state !== undefined) {
|
|
151
226
|
unbindEvents(target, state)
|
|
152
227
|
}
|
|
153
228
|
|