@bagelink/vue 1.6.47 → 1.6.49
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/bin/experimentalGenTypedRoutes.ts +18 -19
- package/bin/utils.ts +4 -4
- package/dist/components/AddressSearch.vue.d.ts.map +1 -1
- package/dist/components/Alert.vue.d.ts.map +1 -1
- package/dist/components/BglVideo.vue.d.ts.map +1 -1
- package/dist/components/Card.vue.d.ts.map +1 -1
- package/dist/components/Carousel.vue.d.ts +2 -2
- package/dist/components/Carousel.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/Flag.vue.d.ts.map +1 -1
- package/dist/components/IframeVue.vue.d.ts.map +1 -1
- package/dist/components/ListItem.vue.d.ts.map +1 -1
- package/dist/components/Loading.vue.d.ts.map +1 -1
- package/dist/components/Modal.vue.d.ts.map +1 -1
- package/dist/components/ModalForm.vue.d.ts.map +1 -1
- package/dist/components/NavBar.vue.d.ts +1 -1
- package/dist/components/Pill.vue.d.ts.map +1 -1
- package/dist/components/Zoomer.vue.d.ts.map +1 -1
- package/dist/components/analytics/LineChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/PieChart.vue.d.ts +2 -1
- package/dist/components/analytics/PieChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/index.d.ts +1 -1
- package/dist/components/analytics/index.d.ts.map +1 -1
- package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
- package/dist/components/form/BglMultiStepForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ColorInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DateInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/PasswordInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RangeInput.vue.d.ts +11 -11
- package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts +1 -0
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/components/layout/Layout.vue.d.ts.map +1 -1
- package/dist/components/layout/Tabs.vue.d.ts.map +1 -1
- package/dist/components/layout/index.d.ts +3 -3
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/index.cjs +24 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +4863 -4781
- package/dist/style.css +1 -1
- package/package.json +1 -2
- package/src/components/AccordionItem.vue +11 -11
- package/src/components/AddToCalendar.vue +1 -1
- package/src/components/AddressSearch.vue +9 -8
- package/src/components/Alert.vue +2 -1
- package/src/components/Badge.vue +5 -5
- package/src/components/BglVideo.vue +44 -45
- package/src/components/Btn.vue +15 -15
- package/src/components/Card.vue +10 -8
- package/src/components/Carousel.vue +159 -162
- package/src/components/DataPreview.vue +1 -1
- package/src/components/DragOver.vue +6 -6
- package/src/components/Dropdown.vue +39 -38
- package/src/components/Flag.vue +7 -6
- package/src/components/Icon/Icon.vue +22 -22
- package/src/components/IframeVue.vue +5 -5
- package/src/components/Image.vue +17 -17
- package/src/components/ImportData.vue +79 -79
- package/src/components/ListItem.vue +12 -11
- package/src/components/Loading.vue +10 -9
- package/src/components/MapEmbed/Index.vue +24 -24
- package/src/components/Modal.vue +11 -9
- package/src/components/ModalForm.vue +9 -8
- package/src/components/NavBar.vue +6 -6
- package/src/components/Pagination.vue +27 -27
- package/src/components/Pill.vue +11 -12
- package/src/components/Rating.vue +2 -2
- package/src/components/Slider.vue +75 -75
- package/src/components/Spreadsheet/Index.vue +34 -34
- package/src/components/Spreadsheet/SpreadsheetTable.vue +3 -3
- package/src/components/Zoomer.vue +165 -168
- package/src/components/analytics/BarChart.vue +6 -6
- package/src/components/analytics/KpiCard.vue +2 -2
- package/src/components/analytics/LineChart.vue +63 -61
- package/src/components/analytics/PieChart.vue +104 -90
- package/src/components/analytics/index.ts +2 -2
- package/src/components/calendar/CalendarPopover.vue +1 -1
- package/src/components/calendar/Index.vue +1 -1
- package/src/components/calendar/views/AgendaView.vue +3 -3
- package/src/components/calendar/views/DayView.vue +6 -6
- package/src/components/calendar/views/MonthView.vue +2 -2
- package/src/components/calendar/views/WeekView.vue +18 -18
- package/src/components/dataTable/DataTable.vue +4 -4
- package/src/components/dataTable/useSorting.ts +1 -1
- package/src/components/dataTable/useTableData.ts +15 -15
- package/src/components/dataTable/useTableSelection.ts +15 -15
- package/src/components/dataTable/useTableVirtualization.ts +1 -1
- package/src/components/draggable/useDraggable.ts +42 -42
- package/src/components/form/BagelForm.vue +15 -15
- package/src/components/form/BglFieldSet.vue +5 -3
- package/src/components/form/BglMultiStepForm.vue +20 -21
- package/src/components/form/inputs/CheckInput.vue +2 -2
- package/src/components/form/inputs/CodeEditor/format.ts +7 -7
- package/src/components/form/inputs/CodeEditor/useHighlight.ts +6 -6
- package/src/components/form/inputs/ColorInput.vue +5 -4
- package/src/components/form/inputs/DateInput.vue +8 -9
- package/src/components/form/inputs/DatePicker.vue +24 -24
- package/src/components/form/inputs/EmailInput.vue +24 -24
- package/src/components/form/inputs/NumberInput.vue +26 -26
- package/src/components/form/inputs/OTP.vue +7 -7
- package/src/components/form/inputs/PasswordInput.vue +3 -2
- package/src/components/form/inputs/RadioGroup.vue +28 -25
- package/src/components/form/inputs/RadioPillsInput.vue +12 -12
- package/src/components/form/inputs/RangeInput.vue +21 -21
- package/src/components/form/inputs/RichText/components/EditorToolbar.vue +107 -92
- package/src/components/form/inputs/RichText/components/TableGridSelector.vue +64 -64
- package/src/components/form/inputs/RichText/components/gridBox.vue +10 -8
- package/src/components/form/inputs/RichText/composables/useCommands.ts +1 -1
- package/src/components/form/inputs/RichText/composables/useEditor.ts +12 -12
- package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +1 -1
- package/src/components/form/inputs/RichText/index.vue +138 -138
- package/src/components/form/inputs/RichText/utils/commands.ts +84 -85
- package/src/components/form/inputs/RichText/utils/debug.ts +1 -1
- package/src/components/form/inputs/RichText/utils/formatting.ts +39 -39
- package/src/components/form/inputs/RichText/utils/selection.ts +28 -28
- package/src/components/form/inputs/RichText/utils/table.ts +19 -19
- package/src/components/form/inputs/SelectBtn.vue +1 -1
- package/src/components/form/inputs/SelectInput.vue +54 -54
- package/src/components/form/inputs/SignaturePad.vue +40 -40
- package/src/components/form/inputs/TableField.vue +1 -1
- package/src/components/form/inputs/TelInput.vue +54 -53
- package/src/components/form/inputs/TextInput.vue +19 -19
- package/src/components/form/inputs/ToggleInput.vue +2 -2
- package/src/components/form/inputs/Upload/useFileUpload.ts +6 -6
- package/src/components/form/useBagelFormState.ts +5 -5
- package/src/components/layout/AppLayout.vue +2 -2
- package/src/components/layout/AppSidebar.vue +77 -16
- package/src/components/layout/Layout.vue +12 -10
- package/src/components/layout/SidebarMenu.vue +4 -4
- package/src/components/layout/TabbedLayout.vue +17 -17
- package/src/components/layout/Tabs.vue +4 -5
- package/src/components/layout/TabsNav.vue +14 -14
- package/src/components/layout/index.ts +3 -5
- package/src/components/lightbox/Lightbox.vue +22 -22
- package/src/components/lightbox/index.ts +8 -8
- package/src/composables/index.ts +8 -8
- package/src/composables/useAddToCalendar.ts +13 -13
- package/src/composables/useDevice.ts +2 -2
- package/src/composables/useFormField.ts +4 -4
- package/src/composables/usePolling.ts +8 -8
- package/src/composables/useSchemaField.ts +38 -38
- package/src/composables/useTheme.ts +9 -9
- package/src/composables/useValidateFieldValue.ts +2 -2
- package/src/directives/pattern.ts +25 -25
- package/src/directives/ripple.ts +4 -4
- package/src/directives/vResize.ts +6 -6
- package/src/index.ts +1 -0
- package/src/plugins/bagel.ts +4 -4
- package/src/styles/layout.css +1 -1
- package/src/types/index.ts +1 -1
- package/src/utils/BagelFormUtils.ts +7 -7
- package/src/utils/calendar/Helpers.ts +8 -8
- package/src/utils/calendar/dateUtils.ts +22 -22
- package/src/utils/calendar/time.ts +25 -25
- package/src/utils/calendar/week.ts +25 -25
- package/src/utils/elementUtils.ts +27 -27
- package/src/utils/sizeParsing.ts +2 -2
- package/src/utils/strings.ts +5 -5
- package/src/utils/tapDetector.ts +11 -11
- package/src/utils/useSearch.ts +29 -29
- package/vite.config.ts +0 -2
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { IconType, Option } from '@bagelink/vue'
|
|
3
3
|
import type { AlignedPlacement } from '../../Dropdown.vue'
|
|
4
4
|
import { Btn, Card, Skeleton, Dropdown, Icon, TextInput, useSearch } from '@bagelink/vue'
|
|
5
|
-
import { onMounted, watch } from 'vue'
|
|
5
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
6
6
|
import 'floating-vue/style.css'
|
|
7
7
|
|
|
8
8
|
type OptionsSource = Option[] | ((query: string) => Promise<Option[]>)
|
|
@@ -35,71 +35,71 @@ interface PropTypes {
|
|
|
35
35
|
searchPlaceholder?: string
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const searchInput =
|
|
39
|
-
const triggerBtn =
|
|
38
|
+
const searchInput = ref<HTMLElement | undefined>()
|
|
39
|
+
const triggerBtn = ref<HTMLButtonElement | undefined>()
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
const selectedItems = ref<Option[]>([])
|
|
42
42
|
|
|
43
|
-
const selectedItemCount =
|
|
44
|
-
const searchTerm =
|
|
43
|
+
const selectedItemCount = computed(() => selectedItems.value.length ?? 0)
|
|
44
|
+
const searchTerm = ref<string>('')
|
|
45
45
|
|
|
46
|
-
const dropdown =
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
const dropdown = ref<InstanceType<typeof Dropdown> | undefined>()
|
|
47
|
+
const selected = ref(false)
|
|
48
|
+
const open = ref(false)
|
|
49
49
|
|
|
50
|
-
const selectedLabel =
|
|
51
|
-
if (selectedItemCount === 0) { return props.placeholder }
|
|
52
|
-
if (selectedItemCount > 4) {
|
|
53
|
-
const str = selectedItems
|
|
50
|
+
const selectedLabel = computed((): string => {
|
|
51
|
+
if (selectedItemCount.value === 0) { return props.placeholder }
|
|
52
|
+
if (selectedItemCount.value > 4) {
|
|
53
|
+
const str = selectedItems.value
|
|
54
54
|
.slice(0, 4)
|
|
55
55
|
.map(item => getLabel(item))
|
|
56
56
|
.join(', ')
|
|
57
|
-
return `${str}... +${selectedItemCount - 4}`
|
|
57
|
+
return `${str}... +${selectedItemCount.value - 4}`
|
|
58
58
|
}
|
|
59
|
-
return selectedItems.map(item => getLabel(item)).join(', ')
|
|
59
|
+
return selectedItems.value.map(item => getLabel(item)).join(', ')
|
|
60
60
|
})
|
|
61
|
-
const searchPlaceholder =
|
|
61
|
+
const searchPlaceholder = computed(() => props.searchPlaceholder ?? selectedLabel.value ?? 'Search')
|
|
62
62
|
|
|
63
63
|
const { results, isLoading } = useSearch<Option>({
|
|
64
|
-
searchTerm: () => searchTerm,
|
|
64
|
+
searchTerm: () => searchTerm.value,
|
|
65
65
|
serverSearch: isAsyncSource(props.options) ? props.options : undefined,
|
|
66
66
|
items: () => (Array.isArray(props.options) ? props.options : []),
|
|
67
67
|
minChars: isAsyncSource(props.options) ? 0 : 2,
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
const highlightedIndex = ref(-1)
|
|
71
71
|
|
|
72
|
-
const selectOptions =
|
|
72
|
+
const selectOptions = ref<HTMLElement | undefined>()
|
|
73
73
|
|
|
74
74
|
function navigate(direction: 'up' | 'down') {
|
|
75
|
-
if (!open) {
|
|
76
|
-
open = true
|
|
75
|
+
if (!open.value) {
|
|
76
|
+
open.value = true
|
|
77
77
|
setTimeout(() => { navigate(direction) }, 210)
|
|
78
78
|
return
|
|
79
79
|
}
|
|
80
80
|
if (direction === 'up') {
|
|
81
|
-
highlightedIndex = highlightedIndex > 0 ? highlightedIndex - 1 : results.value.length - 1
|
|
81
|
+
highlightedIndex.value = highlightedIndex.value > 0 ? highlightedIndex.value - 1 : results.value.length - 1
|
|
82
82
|
} else if (direction === 'down') {
|
|
83
|
-
highlightedIndex = highlightedIndex < results.value.length - 1 ? highlightedIndex + 1 : 0
|
|
83
|
+
highlightedIndex.value = highlightedIndex.value < results.value.length - 1 ? highlightedIndex.value + 1 : 0
|
|
84
84
|
}
|
|
85
85
|
setTimeout(() => {
|
|
86
|
-
const el = selectOptions?.children[highlightedIndex] as HTMLElement
|
|
86
|
+
const el = selectOptions.value?.children[highlightedIndex.value] as HTMLElement
|
|
87
87
|
if (el) { el.focus() }
|
|
88
|
-
else { highlightedIndex = -1 }
|
|
88
|
+
else { highlightedIndex.value = -1 }
|
|
89
89
|
}, 10)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
const isSelected = (option: Option) => selectedItems.find(item => getValue(option) === getValue(item)) !== undefined
|
|
92
|
+
const isSelected = (option: Option) => selectedItems.value.find(item => getValue(option) === getValue(item)) !== undefined
|
|
93
93
|
|
|
94
94
|
function scrollToSelectedItem() {
|
|
95
|
-
if (!selectOptions || selectedItemCount === 0) { return }
|
|
95
|
+
if (!selectOptions.value || selectedItemCount.value === 0) { return }
|
|
96
96
|
|
|
97
97
|
// Find the first selected item in the results
|
|
98
98
|
const selectedIndex = results.value.findIndex(option => isSelected(option))
|
|
99
99
|
if (selectedIndex === -1) { return }
|
|
100
100
|
|
|
101
101
|
// Get the selected option element
|
|
102
|
-
const selectedElement = selectOptions.children[selectedIndex] as HTMLElement
|
|
102
|
+
const selectedElement = selectOptions.value.children[selectedIndex] as HTMLElement
|
|
103
103
|
if (!selectedElement) { return }
|
|
104
104
|
|
|
105
105
|
// Scroll the selected item into view
|
|
@@ -123,47 +123,47 @@ function getValue(option?: Option): Primitive | undefined {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
function focusInput() {
|
|
126
|
-
open = true
|
|
126
|
+
open.value = true
|
|
127
127
|
setTimeout(() => {
|
|
128
|
-
if (searchInput) { searchInput.focus() }
|
|
128
|
+
if (searchInput.value) { searchInput.value.focus() }
|
|
129
129
|
}, 10)
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
function select(option: Option) {
|
|
133
|
-
selected = true
|
|
134
|
-
const existingIndex = selectedItems.findIndex(
|
|
133
|
+
selected.value = true
|
|
134
|
+
const existingIndex = selectedItems.value.findIndex(
|
|
135
135
|
item => getValue(item) === getValue(option),
|
|
136
136
|
)
|
|
137
137
|
if (existingIndex > -1) {
|
|
138
|
-
selectedItems.splice(existingIndex, 1)
|
|
138
|
+
selectedItems.value.splice(existingIndex, 1)
|
|
139
139
|
}
|
|
140
140
|
else if (props.multiselect) {
|
|
141
|
-
const current = [...selectedItems]
|
|
141
|
+
const current = [...selectedItems.value]
|
|
142
142
|
current.push(option)
|
|
143
143
|
|
|
144
|
-
selectedItems = current
|
|
144
|
+
selectedItems.value = current
|
|
145
145
|
} else {
|
|
146
|
-
selectedItems.splice(0, selectedItemCount, option)
|
|
146
|
+
selectedItems.value.splice(0, selectedItemCount.value, option)
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// Move focus away from popper content before it gets aria-hidden
|
|
150
150
|
const active = document.activeElement as HTMLElement | null
|
|
151
|
-
if (active && selectOptions?.contains(active)) { active.blur() }
|
|
151
|
+
if (active && selectOptions.value?.contains(active)) { active.blur() }
|
|
152
152
|
|
|
153
153
|
if (!props.multiselect) {
|
|
154
|
-
open = false
|
|
154
|
+
open.value = false
|
|
155
155
|
// After closing, return focus to the trigger for accessibility
|
|
156
|
-
setTimeout(() => { triggerBtn?.focus() }, 0)
|
|
156
|
+
setTimeout(() => { triggerBtn.value?.focus() }, 0)
|
|
157
157
|
}
|
|
158
158
|
emitUpdate()
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
function emitUpdate() {
|
|
162
162
|
if (props.multiselect) {
|
|
163
|
-
emit('update:modelValue', selectedItems.map(getValue).filter(Boolean))
|
|
163
|
+
emit('update:modelValue', selectedItems.value.map(getValue).filter(Boolean))
|
|
164
164
|
} else {
|
|
165
|
-
// selectedItems.splice(1, selectedItemCount - 1);
|
|
166
|
-
const [item] = selectedItems
|
|
165
|
+
// selectedItems.value.splice(1, selectedItemCount.value - 1);
|
|
166
|
+
const [item] = selectedItems.value
|
|
167
167
|
emit('update:modelValue', getValue(item))
|
|
168
168
|
}
|
|
169
169
|
}
|
|
@@ -182,21 +182,21 @@ watch(
|
|
|
182
182
|
const newOption = Array.isArray(props.options)
|
|
183
183
|
? (props.options.find(o => getValue(o) === newVal) ?? newVal)
|
|
184
184
|
: newVal
|
|
185
|
-
if (newOption && !isSelected(newOption)) { selectedItems = [newOption] }
|
|
185
|
+
if (newOption && !isSelected(newOption)) { selectedItems.value = [newOption] }
|
|
186
186
|
} else {
|
|
187
187
|
const newData = [newVal].flat()
|
|
188
|
-
const isSame = compareArrays(newData, selectedItems)
|
|
188
|
+
const isSame = compareArrays(newData, selectedItems.value)
|
|
189
189
|
if (!isSame) {
|
|
190
|
-
selectedItems.splice(0, selectedItemCount, ...newData)
|
|
190
|
+
selectedItems.value.splice(0, selectedItemCount.value, ...newData)
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
},
|
|
194
194
|
{ immediate: true },
|
|
195
195
|
)
|
|
196
196
|
|
|
197
|
-
watch(() => open, (value) => {
|
|
197
|
+
watch(() => open.value, (value) => {
|
|
198
198
|
if (value) {
|
|
199
|
-
highlightedIndex = -1
|
|
199
|
+
highlightedIndex.value = -1
|
|
200
200
|
// Scroll to selected item when dropdown opens
|
|
201
201
|
setTimeout(() => { scrollToSelectedItem() }, 10)
|
|
202
202
|
}
|
|
@@ -207,15 +207,15 @@ watch(
|
|
|
207
207
|
() => {
|
|
208
208
|
const opts = props.options
|
|
209
209
|
if (!Array.isArray(opts)) { return }
|
|
210
|
-
selectedItems.forEach((option, i) => {
|
|
210
|
+
selectedItems.value.forEach((option, i) => {
|
|
211
211
|
const exists = opts.find(
|
|
212
212
|
(o: Option) => getValue(o) === getValue(option),
|
|
213
213
|
)
|
|
214
|
-
if (exists === undefined) { selectedItems.splice(i, 1) }
|
|
215
|
-
else { selectedItems.splice(i, 1, exists) }
|
|
214
|
+
if (exists === undefined) { selectedItems.value.splice(i, 1) }
|
|
215
|
+
else { selectedItems.value.splice(i, 1, exists) }
|
|
216
216
|
})
|
|
217
217
|
// const original = JSON.stringify(props.options.map(getValue));
|
|
218
|
-
// const newSelection = JSON.stringify(selectedItems.map(getValue));
|
|
218
|
+
// const newSelection = JSON.stringify(selectedItems.value.map(getValue));
|
|
219
219
|
// if (original !== newSelection) emitUpdate();
|
|
220
220
|
},
|
|
221
221
|
{ deep: true, immediate: true },
|
|
@@ -226,7 +226,7 @@ watch(
|
|
|
226
226
|
() => results.value,
|
|
227
227
|
(newResults) => {
|
|
228
228
|
if (isAsyncSource(props.options) && newResults.length > 0) {
|
|
229
|
-
selectedItems.forEach((option, i) => {
|
|
229
|
+
selectedItems.value.forEach((option, i) => {
|
|
230
230
|
const optionValue = getValue(option)
|
|
231
231
|
// If the selected item is just a primitive value (no label), find the full option
|
|
232
232
|
if (typeof option !== 'object' || !option.label) {
|
|
@@ -234,7 +234,7 @@ watch(
|
|
|
234
234
|
(o: Option) => getValue(o) === optionValue,
|
|
235
235
|
)
|
|
236
236
|
if (fullOption) {
|
|
237
|
-
selectedItems.splice(i, 1, fullOption)
|
|
237
|
+
selectedItems.value.splice(i, 1, fullOption)
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
})
|
|
@@ -252,7 +252,7 @@ onMounted(() => {
|
|
|
252
252
|
|
|
253
253
|
if (defaultOption === undefined) { return }
|
|
254
254
|
|
|
255
|
-
selectedItems = [defaultOption]
|
|
255
|
+
selectedItems.value = [defaultOption]
|
|
256
256
|
}
|
|
257
257
|
})
|
|
258
258
|
</script>
|
|
@@ -3,7 +3,7 @@ import type { Options as SignaturePadOptions } from 'signature_pad'
|
|
|
3
3
|
import { Btn } from '@bagelink/vue'
|
|
4
4
|
import { useEventListener, useResizeObserver } from '@vueuse/core'
|
|
5
5
|
import SignaturePad from 'signature_pad'
|
|
6
|
-
import { onMounted, watch } from 'vue'
|
|
6
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
7
7
|
|
|
8
8
|
export interface WaterMark {
|
|
9
9
|
text: string
|
|
@@ -38,35 +38,35 @@ const props = withDefaults(defineProps<{
|
|
|
38
38
|
const sigData = defineModel()
|
|
39
39
|
const fileData = defineModel<File>('file')
|
|
40
40
|
|
|
41
|
-
const vCanvas =
|
|
41
|
+
const vCanvas = ref<HTMLCanvasElement>()
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
const sig = ref<SignaturePad>()
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
const _isEmpty = ref<boolean>(true)
|
|
46
46
|
|
|
47
47
|
function clear() {
|
|
48
|
-
sig?.clear()
|
|
49
|
-
_isEmpty = true
|
|
48
|
+
sig.value?.clear()
|
|
49
|
+
_isEmpty.value = true
|
|
50
50
|
sigData.value = undefined
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function save(format?: FormatType) {
|
|
54
54
|
format = format || props.format
|
|
55
|
-
return format ? sig?.toDataURL(format) : sig?.toDataURL()
|
|
55
|
+
return format ? sig.value?.toDataURL(format) : sig.value?.toDataURL()
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function saveFile() {
|
|
59
59
|
const data = save()
|
|
60
|
-
if (!data) {return}
|
|
60
|
+
if (!data) { return }
|
|
61
61
|
|
|
62
|
-
if ('image/svg+xml'
|
|
62
|
+
if (props.format === 'image/svg+xml') {
|
|
63
63
|
// For SVG, create file directly from the SVG string
|
|
64
64
|
fileData.value = new File([data], 'signature.svg', { type: 'image/svg+xml' })
|
|
65
65
|
} else {
|
|
66
66
|
try {
|
|
67
67
|
// For PNG/JPG, convert base64 to blob first
|
|
68
68
|
const base64Data = data.split(',')[1]
|
|
69
|
-
if (!base64Data) {throw new Error('Invalid data URL format')}
|
|
69
|
+
if (!base64Data) { throw new Error('Invalid data URL format') }
|
|
70
70
|
|
|
71
71
|
const byteCharacters = atob(base64Data)
|
|
72
72
|
// Create byte array directly with mapping function
|
|
@@ -86,41 +86,41 @@ function saveFile() {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function fromDataURL(url: string) {
|
|
89
|
-
return sig?.fromDataURL(url, {
|
|
90
|
-
height: vCanvas?.height,
|
|
91
|
-
width: vCanvas?.width,
|
|
89
|
+
return sig.value?.fromDataURL(url, {
|
|
90
|
+
height: vCanvas.value?.height,
|
|
91
|
+
width: vCanvas.value?.width,
|
|
92
92
|
ratio: props.ratio,
|
|
93
93
|
xOffset: 0,
|
|
94
94
|
yOffset: 0,
|
|
95
95
|
})
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
const isEmpty = () => sig?.isEmpty()
|
|
98
|
+
const isEmpty = () => sig.value?.isEmpty()
|
|
99
99
|
function undo() {
|
|
100
|
-
const data = sig?.toData()
|
|
100
|
+
const data = sig.value?.toData()
|
|
101
101
|
if (data) {
|
|
102
102
|
data.pop()
|
|
103
|
-
sig?.fromData(data)
|
|
103
|
+
sig.value?.fromData(data)
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
const defaultOptions =
|
|
107
|
+
const defaultOptions = ref<SignaturePadOptions>({
|
|
108
108
|
penColor: 'rgb(0, 0, 0)',
|
|
109
109
|
backgroundColor: 'rgb(255, 255, 255)',
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
-
const signatureOptions =
|
|
112
|
+
const signatureOptions = computed(() => ({ ...defaultOptions.value, ...props.sigOption }))
|
|
113
113
|
|
|
114
114
|
watch(
|
|
115
115
|
() => props.disabled,
|
|
116
116
|
(val) => {
|
|
117
|
-
if (val) {sig?.off()}
|
|
118
|
-
else {sig?.on()}
|
|
117
|
+
if (val) { sig.value?.off() }
|
|
118
|
+
else { sig.value?.on() }
|
|
119
119
|
},
|
|
120
120
|
)
|
|
121
121
|
|
|
122
122
|
function addWaterMark(data: WaterMark) {
|
|
123
|
-
if (!(
|
|
123
|
+
if (!(Object.prototype.toString.call(data) === '[object Object]')) {
|
|
124
124
|
throw new Error(`Expected Object, got ${typeof data}.`)
|
|
125
125
|
} else {
|
|
126
126
|
const textData = {
|
|
@@ -131,15 +131,15 @@ function addWaterMark(data: WaterMark) {
|
|
|
131
131
|
sy: data.sy || 40,
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
const ctx = vCanvas?.getContext('2d')
|
|
135
|
-
if (!ctx) {return}
|
|
134
|
+
const ctx = vCanvas.value?.getContext('2d')
|
|
135
|
+
if (!ctx) { return }
|
|
136
136
|
ctx.font = data.font || '20px sans-serif'
|
|
137
137
|
ctx.fillStyle = data.fillStyle || '#333'
|
|
138
138
|
ctx.strokeStyle = data.strokeStyle || '#333'
|
|
139
|
-
if ('all'
|
|
139
|
+
if (data.style === 'all') {
|
|
140
140
|
ctx.fillText(textData.text, textData.x, textData.y)
|
|
141
141
|
ctx.strokeText(textData.text, textData.sx, textData.sy)
|
|
142
|
-
} else if ('stroke'
|
|
142
|
+
} else if (data.style === 'stroke') {
|
|
143
143
|
ctx.strokeText(textData.text, textData.sx, textData.sy)
|
|
144
144
|
} else {
|
|
145
145
|
ctx.fillText(textData.text, textData.x, textData.y)
|
|
@@ -148,20 +148,20 @@ function addWaterMark(data: WaterMark) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
function onEndStroke() {
|
|
151
|
-
_isEmpty = sig?.isEmpty() ?? false
|
|
151
|
+
_isEmpty.value = sig.value?.isEmpty() ?? false
|
|
152
152
|
const data = save()
|
|
153
153
|
sigData.value = data
|
|
154
154
|
saveFile()
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
function loadFile(file: File | undefined) {
|
|
158
|
-
if (!file) {return}
|
|
158
|
+
if (!file) { return }
|
|
159
159
|
const reader = new FileReader()
|
|
160
160
|
return new Promise((resolve) => {
|
|
161
161
|
reader.onload = (e) => {
|
|
162
162
|
const dataUrl = String(e.target?.result)
|
|
163
163
|
void fromDataURL(dataUrl)
|
|
164
|
-
_isEmpty = false
|
|
164
|
+
_isEmpty.value = false
|
|
165
165
|
sigData.value = dataUrl
|
|
166
166
|
resolve(true)
|
|
167
167
|
}
|
|
@@ -175,22 +175,22 @@ watch(fileData, (newFile) => {
|
|
|
175
175
|
|
|
176
176
|
function resizeCanvas() {
|
|
177
177
|
let url
|
|
178
|
-
if (!isEmpty()) {url = save()}
|
|
178
|
+
if (!isEmpty()) { url = save() }
|
|
179
179
|
const ratio = props.ratio ?? 1 / 2
|
|
180
|
-
const { width } = vCanvas!.getBoundingClientRect()
|
|
181
|
-
vCanvas!.width = props.width ? Number(props.width.replaceAll('px', '')) : width
|
|
182
|
-
vCanvas!.height = props.height ? Number(props.height.replaceAll('px', '')) : width * ratio
|
|
180
|
+
const { width } = vCanvas.value!.getBoundingClientRect()
|
|
181
|
+
vCanvas.value!.width = props.width ? Number(props.width.replaceAll('px', '')) : width
|
|
182
|
+
vCanvas.value!.height = props.height ? Number(props.height.replaceAll('px', '')) : width * ratio
|
|
183
183
|
clear()
|
|
184
|
-
if (!props.clearOnResize && url !== undefined) {void fromDataURL(url)}
|
|
185
|
-
if (props.waterMark) {addWaterMark(props.waterMark)}
|
|
184
|
+
if (!props.clearOnResize && url !== undefined) { void fromDataURL(url) }
|
|
185
|
+
if (props.waterMark) { addWaterMark(props.waterMark) }
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
function draw() {
|
|
189
|
-
sig = new SignaturePad(vCanvas!, signatureOptions)
|
|
189
|
+
sig.value = new SignaturePad(vCanvas.value!, signatureOptions.value)
|
|
190
190
|
resizeCanvas()
|
|
191
|
-
if (props.disabled) {sig.off()}
|
|
192
|
-
else {sig.on()}
|
|
193
|
-
_isEmpty = sig.isEmpty()
|
|
191
|
+
if (props.disabled) { sig.value.off() }
|
|
192
|
+
else { sig.value.on() }
|
|
193
|
+
_isEmpty.value = sig.value.isEmpty()
|
|
194
194
|
|
|
195
195
|
// Load initial file if provided
|
|
196
196
|
if (fileData.value) {
|
|
@@ -199,8 +199,8 @@ function draw() {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
onMounted(draw)
|
|
202
|
-
useResizeObserver(() => vCanvas, resizeCanvas)
|
|
203
|
-
useEventListener(() => sig, 'endStroke', onEndStroke)
|
|
202
|
+
useResizeObserver(() => vCanvas.value, resizeCanvas)
|
|
203
|
+
useEventListener(() => sig.value, 'endStroke', onEndStroke)
|
|
204
204
|
|
|
205
205
|
defineExpose({
|
|
206
206
|
save,
|
|
@@ -40,7 +40,7 @@ function addRow() {
|
|
|
40
40
|
} as { [key: string]: any }
|
|
41
41
|
entityMeta?.fields.forEach((field: any) => {
|
|
42
42
|
if (field.default) {
|
|
43
|
-
if ('Check'
|
|
43
|
+
if (field.fieldtype === 'Check') {
|
|
44
44
|
entry[field.fieldname] = Number.parseInt(field.default)
|
|
45
45
|
} else {
|
|
46
46
|
entry[field.fieldname] = field.default
|
|
@@ -3,7 +3,7 @@ import type { Country } from '@bagelink/vue'
|
|
|
3
3
|
import type { CountryCode } from 'libphonenumber-js'
|
|
4
4
|
import { Dropdown, Flag, Icon, TextInput, allCountries, ipapi } from '@bagelink/vue'
|
|
5
5
|
import { parsePhoneNumberFromString } from 'libphonenumber-js'
|
|
6
|
-
import {
|
|
6
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
7
7
|
|
|
8
8
|
const props = defineProps<{
|
|
9
9
|
id?: string
|
|
@@ -18,27 +18,27 @@ const props = defineProps<{
|
|
|
18
18
|
|
|
19
19
|
const emit = defineEmits(['update:modelValue', 'blur', 'focus', 'keydown', 'input', 'paste'])
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const searchInput =
|
|
25
|
-
|
|
21
|
+
const phoneNumber = ref(props.modelValue ?? '')
|
|
22
|
+
const search = ref('')
|
|
23
|
+
const activeCountry = ref<Country>()
|
|
24
|
+
const searchInput = ref<InstanceType<typeof TextInput>>()
|
|
25
|
+
const open = ref(false)
|
|
26
26
|
const inputRef = ref<HTMLInputElement | null>(null)
|
|
27
|
-
|
|
27
|
+
const isValid = ref(true)
|
|
28
28
|
|
|
29
29
|
// Watch for changes to phoneNumber and emit update events
|
|
30
|
-
watch(() => phoneNumber, (newValue) => {
|
|
30
|
+
watch(() => phoneNumber.value, (newValue) => {
|
|
31
31
|
emit('update:modelValue', newValue)
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
// Watch for changes to the modelValue prop
|
|
35
35
|
watch(() => props.modelValue, (newValue) => {
|
|
36
|
-
if (newValue !== phoneNumber) {
|
|
37
|
-
phoneNumber = newValue ?? ''
|
|
36
|
+
if (newValue !== phoneNumber.value) {
|
|
37
|
+
phoneNumber.value = newValue ?? ''
|
|
38
38
|
}
|
|
39
39
|
})
|
|
40
40
|
|
|
41
|
-
const countries =
|
|
41
|
+
const countries = computed(() => {
|
|
42
42
|
let filteredCountries = allCountries
|
|
43
43
|
if (props.excludeCountries && props.excludeCountries.length) {
|
|
44
44
|
const excludeCountries = props.excludeCountries.map(c => c.toLowerCase())
|
|
@@ -48,52 +48,52 @@ const countries = $computed(() => {
|
|
|
48
48
|
const onlyCountries = props.onlyCountries.map(c => c.toLowerCase())
|
|
49
49
|
filteredCountries = filteredCountries.filter(c => onlyCountries.includes(c.iso2.toLowerCase()))
|
|
50
50
|
}
|
|
51
|
-
if (search.length) {
|
|
52
|
-
const lowerCaseSearch = search.toLowerCase()
|
|
51
|
+
if (search.value.length) {
|
|
52
|
+
const lowerCaseSearch = search.value.toLowerCase()
|
|
53
53
|
filteredCountries = filteredCountries.filter(c => c.name.toLowerCase().includes(lowerCaseSearch)
|
|
54
54
|
|| c.iso2.toLowerCase().includes(lowerCaseSearch)
|
|
55
|
-
|| c.dialCode.includes(search.replace('+', ''))
|
|
55
|
+
|| c.dialCode.includes(search.value.replace('+', ''))
|
|
56
56
|
)
|
|
57
57
|
}
|
|
58
58
|
return filteredCountries
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
-
const activeCountryCode =
|
|
61
|
+
const activeCountryCode = computed(() => activeCountry.value?.iso2)
|
|
62
62
|
|
|
63
63
|
function selectCountry(country: Country) {
|
|
64
|
-
activeCountry = country
|
|
65
|
-
open = false
|
|
66
|
-
search = ''
|
|
64
|
+
activeCountry.value = country
|
|
65
|
+
open.value = false
|
|
66
|
+
search.value = ''
|
|
67
67
|
|
|
68
|
-
if (!phoneNumber) {
|
|
69
|
-
phoneNumber = `+${activeCountry.dialCode}`
|
|
70
|
-
} else if (phoneNumber.startsWith('+')) {
|
|
68
|
+
if (!phoneNumber.value) {
|
|
69
|
+
phoneNumber.value = `+${activeCountry.value.dialCode}`
|
|
70
|
+
} else if (phoneNumber.value.startsWith('+')) {
|
|
71
71
|
// Replace existing country code with the new one
|
|
72
|
-
const existingCountry = countries.find(c => phoneNumber.startsWith(`+${c.dialCode}`))
|
|
72
|
+
const existingCountry = countries.value.find(c => phoneNumber.value.startsWith(`+${c.dialCode}`))
|
|
73
73
|
if (existingCountry && existingCountry !== country) {
|
|
74
74
|
// Remove the old country code and replace with new one
|
|
75
|
-
const nationalPart = phoneNumber.substring(`+${existingCountry.dialCode}`.length).trim()
|
|
76
|
-
phoneNumber = `+${activeCountry.dialCode}${nationalPart ? ` ${nationalPart}` : ''}`
|
|
75
|
+
const nationalPart = phoneNumber.value.substring(`+${existingCountry.dialCode}`.length).trim()
|
|
76
|
+
phoneNumber.value = `+${activeCountry.value.dialCode}${nationalPart ? ` ${nationalPart}` : ''}`
|
|
77
77
|
}
|
|
78
78
|
} else {
|
|
79
79
|
// Phone number doesn't start with +, prepend the country code
|
|
80
|
-
phoneNumber = `+${activeCountry.dialCode} ${phoneNumber.replace(/^0+/, '')}`
|
|
80
|
+
phoneNumber.value = `+${activeCountry.value.dialCode} ${phoneNumber.value.replace(/^0+/, '')}`
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async function getIp() {
|
|
85
85
|
const { country_code } = await ipapi()
|
|
86
|
-
selectCountry(countries.find(c => c.iso2 === country_code) ?? countries[0])
|
|
86
|
+
selectCountry(countries.value.find(c => c.iso2 === country_code) ?? countries.value[0])
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// Get the country code for use with libphonenumber-js
|
|
90
90
|
function getCountryCode(): CountryCode | undefined {
|
|
91
|
-
return activeCountry?.iso2 ? activeCountry.iso2.toUpperCase() as CountryCode : undefined
|
|
91
|
+
return activeCountry.value?.iso2 ? activeCountry.value.iso2.toUpperCase() as CountryCode : undefined
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// Parse and format the phone number
|
|
95
95
|
function parseAndFormatPhoneNumber(value: string): string {
|
|
96
|
-
if (!value) {return value}
|
|
96
|
+
if (!value) { return value }
|
|
97
97
|
|
|
98
98
|
try {
|
|
99
99
|
const parsedNumber = parsePhoneNumberFromString(value, getCountryCode())
|
|
@@ -108,31 +108,32 @@ function parseAndFormatPhoneNumber(value: string): string {
|
|
|
108
108
|
|
|
109
109
|
// Validate the phone number and set custom validity
|
|
110
110
|
function validatePhoneNumber() {
|
|
111
|
-
if (!inputRef.value) {return}
|
|
111
|
+
if (!inputRef.value) { return }
|
|
112
112
|
|
|
113
113
|
try {
|
|
114
|
-
const parsedNumber = parsePhoneNumberFromString(phoneNumber, getCountryCode())
|
|
114
|
+
const parsedNumber = parsePhoneNumberFromString(phoneNumber.value, getCountryCode())
|
|
115
115
|
if (parsedNumber && parsedNumber.isValid()) {
|
|
116
116
|
inputRef.value.setCustomValidity('')
|
|
117
|
-
isValid = true
|
|
117
|
+
isValid.value = true
|
|
118
118
|
} else {
|
|
119
119
|
inputRef.value.setCustomValidity('Please enter a valid phone number')
|
|
120
|
-
isValid = false
|
|
120
|
+
isValid.value = false
|
|
121
121
|
}
|
|
122
122
|
} catch (error) {
|
|
123
|
+
console.error('Error validating phone number:', error)
|
|
123
124
|
inputRef.value.setCustomValidity('Please enter a valid phone number')
|
|
124
|
-
isValid = false
|
|
125
|
+
isValid.value = false
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
function detectCountryFromNumber(value: string): boolean {
|
|
129
|
-
if (!value.startsWith('+')) {return false}
|
|
130
|
+
if (!value.startsWith('+')) { return false }
|
|
130
131
|
|
|
131
132
|
const digits = value.replace(/\D/g, '')
|
|
132
|
-
if (
|
|
133
|
+
if (digits.length <= 1) { return false }
|
|
133
134
|
|
|
134
|
-
for (const country of countries) {
|
|
135
|
-
if (digits.startsWith(country.dialCode) && country !== activeCountry) {
|
|
135
|
+
for (const country of countries.value) {
|
|
136
|
+
if (digits.startsWith(country.dialCode) && country !== activeCountry.value) {
|
|
136
137
|
selectCountry(country)
|
|
137
138
|
return true
|
|
138
139
|
}
|
|
@@ -141,15 +142,15 @@ function detectCountryFromNumber(value: string): boolean {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
async function initializeCountry() {
|
|
144
|
-
if (phoneNumber) {
|
|
145
|
-
detectCountryFromNumber(phoneNumber)
|
|
145
|
+
if (phoneNumber.value) {
|
|
146
|
+
detectCountryFromNumber(phoneNumber.value)
|
|
146
147
|
} else {
|
|
147
148
|
await getIp()
|
|
148
149
|
}
|
|
149
|
-
const formatted = parseAndFormatPhoneNumber(phoneNumber)
|
|
150
|
-
if (formatted !== phoneNumber) {
|
|
151
|
-
phoneNumber = formatted
|
|
152
|
-
emit('input', phoneNumber)
|
|
150
|
+
const formatted = parseAndFormatPhoneNumber(phoneNumber.value)
|
|
151
|
+
if (formatted !== phoneNumber.value) {
|
|
152
|
+
phoneNumber.value = formatted
|
|
153
|
+
emit('input', phoneNumber.value)
|
|
153
154
|
validatePhoneNumber()
|
|
154
155
|
}
|
|
155
156
|
validatePhoneNumber()
|
|
@@ -163,35 +164,35 @@ function handlePhoneInput(event: Event) {
|
|
|
163
164
|
if (value.startsWith('+')) {
|
|
164
165
|
const formatted = parseAndFormatPhoneNumber(value)
|
|
165
166
|
if (formatted !== value) {
|
|
166
|
-
phoneNumber = formatted
|
|
167
|
+
phoneNumber.value = formatted
|
|
167
168
|
emit('input', event)
|
|
168
169
|
validatePhoneNumber()
|
|
169
170
|
return
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
phoneNumber = value
|
|
174
|
+
phoneNumber.value = value
|
|
174
175
|
emit('input', event)
|
|
175
176
|
validatePhoneNumber()
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
function handleBlur(event: Event) {
|
|
179
|
-
if (phoneNumber && !phoneNumber.startsWith('+') && activeCountry) {
|
|
180
|
-
const nationalNumber = phoneNumber.replace(/^0+/, '')
|
|
181
|
-
phoneNumber = `+${activeCountry.dialCode} ${nationalNumber}`
|
|
182
|
-
} else if (phoneNumber) {
|
|
183
|
-
phoneNumber = parseAndFormatPhoneNumber(phoneNumber)
|
|
180
|
+
if (phoneNumber.value && !phoneNumber.value.startsWith('+') && activeCountry.value) {
|
|
181
|
+
const nationalNumber = phoneNumber.value.replace(/^0+/, '')
|
|
182
|
+
phoneNumber.value = `+${activeCountry.value.dialCode} ${nationalNumber}`
|
|
183
|
+
} else if (phoneNumber.value) {
|
|
184
|
+
phoneNumber.value = parseAndFormatPhoneNumber(phoneNumber.value)
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
validatePhoneNumber()
|
|
187
188
|
emit('blur', event)
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
const disableDropdown =
|
|
191
|
-
const searchable =
|
|
191
|
+
const disableDropdown = computed(() => countries.value.length === 1 && !search.value)
|
|
192
|
+
const searchable = computed(() => countries.value.length > 7 || search.value)
|
|
192
193
|
|
|
193
194
|
function focusSearchInput() {
|
|
194
|
-
setTimeout(() => searchInput?.focus(), 100)
|
|
195
|
+
setTimeout(() => searchInput.value?.focus(), 100)
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
onMounted(initializeCountry)
|