@stonecrop/aform 0.31.0 → 0.33.0
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/aform.js +3024 -2256
- package/dist/aform.js.map +1 -1
- package/dist/assets/index.css +1170 -1
- package/dist/src/components/AForm.vue.d.ts +20 -0
- package/dist/src/components/AForm.vue.d.ts.map +1 -0
- package/dist/src/components/AFormLoading.vue.d.ts +14 -0
- package/dist/src/components/AFormLoading.vue.d.ts.map +1 -0
- package/dist/src/components/base/CollapseButton.vue.d.ts +7 -0
- package/dist/src/components/base/CollapseButton.vue.d.ts.map +1 -0
- package/dist/src/components/base/ExpandButton.vue.d.ts +7 -0
- package/dist/src/components/base/ExpandButton.vue.d.ts.map +1 -0
- package/dist/src/components/form/ABadge.vue.d.ts +14 -0
- package/dist/src/components/form/ABadge.vue.d.ts.map +1 -0
- package/dist/src/components/form/ACheckbox.vue.d.ts +15 -0
- package/dist/src/components/form/ACheckbox.vue.d.ts.map +1 -0
- package/dist/src/components/form/ACurrencyInput.vue.d.ts +20 -0
- package/dist/src/components/form/ACurrencyInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADate.vue.d.ts +14 -0
- package/dist/src/components/form/ADate.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADatePicker.vue.d.ts +28 -0
- package/dist/src/components/form/ADatePicker.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateRange.vue.d.ts +18 -0
- package/dist/src/components/form/ADateRange.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateSelection.vue.d.ts +52 -0
- package/dist/src/components/form/ADateSelection.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateTime.vue.d.ts +17 -0
- package/dist/src/components/form/ADateTime.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateTimeInput.vue.d.ts +30 -0
- package/dist/src/components/form/ADateTimeInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADropdown.vue.d.ts +20 -0
- package/dist/src/components/form/ADropdown.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADuration.vue.d.ts +18 -0
- package/dist/src/components/form/ADuration.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFieldset.vue.d.ts +28 -0
- package/dist/src/components/form/AFieldset.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFileAttach.vue.d.ts +5 -0
- package/dist/src/components/form/AFileAttach.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFormLink.vue.d.ts +40 -0
- package/dist/src/components/form/AFormLink.vue.d.ts.map +1 -0
- package/dist/src/components/form/ANumericInput.vue.d.ts +14 -0
- package/dist/src/components/form/ANumericInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/AQuantityInput.vue.d.ts +20 -0
- package/dist/src/components/form/AQuantityInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ASegmentedControl.vue.d.ts +23 -0
- package/dist/src/components/form/ASegmentedControl.vue.d.ts.map +1 -0
- package/dist/src/components/form/ATextInput.vue.d.ts +14 -0
- package/dist/src/components/form/ATextInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ATextboxInput.vue.d.ts +21 -0
- package/dist/src/components/form/ATextboxInput.vue.d.ts.map +1 -0
- package/dist/src/components/utilities/Login.vue.d.ts +14 -0
- package/dist/src/components/utilities/Login.vue.d.ts.map +1 -0
- package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
- package/package.json +35 -30
- package/dist/aform.d.ts +0 -441
- package/dist/aform.tsbuildinfo +0 -1
- package/dist/src/directives/mask.js +0 -107
- package/dist/src/index.js +0 -58
- package/dist/src/types/index.js +0 -0
- package/dist/src/utils/badge.js +0 -71
- package/dist/src/utils/columns.js +0 -33
- package/dist/src/utils/deserialize.js +0 -20
- package/src/components/AForm.vue +0 -314
- package/src/components/AFormLoading.vue +0 -76
- package/src/components/base/CollapseButton.vue +0 -36
- package/src/components/base/ExpandButton.vue +0 -34
- package/src/components/form/ABadge.vue +0 -135
- package/src/components/form/ACheckbox.vue +0 -62
- package/src/components/form/ACurrencyInput.vue +0 -357
- package/src/components/form/ADate.vue +0 -93
- package/src/components/form/ADatePicker.vue +0 -470
- package/src/components/form/ADateRange.vue +0 -133
- package/src/components/form/ADateSelection.vue +0 -174
- package/src/components/form/ADateTime.vue +0 -149
- package/src/components/form/ADateTimeInput.vue +0 -299
- package/src/components/form/ADropdown.vue +0 -222
- package/src/components/form/ADuration.vue +0 -124
- package/src/components/form/AFieldset.vue +0 -72
- package/src/components/form/AFileAttach.vue +0 -97
- package/src/components/form/AFormLink.vue +0 -368
- package/src/components/form/ANumericInput.vue +0 -32
- package/src/components/form/AQuantityInput.vue +0 -349
- package/src/components/form/ASegmentedControl.vue +0 -342
- package/src/components/form/ATextInput.vue +0 -40
- package/src/components/form/ATextboxInput.vue +0 -66
- package/src/components/utilities/Login.vue +0 -189
- package/src/directives/mask.ts +0 -122
- package/src/index.ts +0 -92
- package/src/shims-vue.d.ts +0 -5
- package/src/types/index.ts +0 -333
- package/src/utils/badge.ts +0 -94
- package/src/utils/columns.ts +0 -38
- package/src/utils/deserialize.ts +0 -20
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
<!-- This serves as a wrapper component for ADatePicker and ADateTimeInput in one component -->
|
|
2
|
-
|
|
3
|
-
<template>
|
|
4
|
-
<div class="adate-selection">
|
|
5
|
-
<ADatePicker v-if="showDate" :select-range="selectRange" @get-date="handleDate" />
|
|
6
|
-
|
|
7
|
-
<ADateTimeInput
|
|
8
|
-
v-if="showTime"
|
|
9
|
-
:allow-military-time="allowMilitaryTime"
|
|
10
|
-
:default-hours="defaultHours"
|
|
11
|
-
:default-minutes="defaultMinutes"
|
|
12
|
-
:default-seconds="defaultSeconds"
|
|
13
|
-
:default-meridiem="defaultMeridiem"
|
|
14
|
-
:use-seconds="useSeconds"
|
|
15
|
-
@get-time="handleStartTime" />
|
|
16
|
-
|
|
17
|
-
<template v-if="selectRange && showTime && showEndTime">
|
|
18
|
-
<div class="adate-selection__end-label">End time</div>
|
|
19
|
-
<ADateTimeInput
|
|
20
|
-
:allow-military-time="allowMilitaryTime"
|
|
21
|
-
:default-hours="defaultHours"
|
|
22
|
-
:default-minutes="defaultMinutes"
|
|
23
|
-
:default-seconds="defaultSeconds"
|
|
24
|
-
:default-meridiem="defaultMeridiem"
|
|
25
|
-
:use-seconds="useSeconds"
|
|
26
|
-
@get-time="handleEndTime" />
|
|
27
|
-
</template>
|
|
28
|
-
|
|
29
|
-
<p v-if="!showTime && !showDate" class="empty">empty</p>
|
|
30
|
-
</div>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<script setup lang="ts">
|
|
34
|
-
import { provide, ref } from 'vue'
|
|
35
|
-
import ADatePicker from './ADatePicker.vue'
|
|
36
|
-
import ADateTimeInput from './ADateTimeInput.vue'
|
|
37
|
-
|
|
38
|
-
const {
|
|
39
|
-
showDate = true,
|
|
40
|
-
showTime = true,
|
|
41
|
-
selectRange = true,
|
|
42
|
-
showEndTime = false,
|
|
43
|
-
allowMilitaryTime = false,
|
|
44
|
-
defaultHours = 12,
|
|
45
|
-
defaultMinutes = 0,
|
|
46
|
-
defaultSeconds = 0,
|
|
47
|
-
defaultMeridiem = 'AM',
|
|
48
|
-
useSeconds = true,
|
|
49
|
-
} = defineProps<{
|
|
50
|
-
showDate?: boolean
|
|
51
|
-
showTime?: boolean
|
|
52
|
-
selectRange?: boolean
|
|
53
|
-
showEndTime?: boolean
|
|
54
|
-
allowMilitaryTime?: boolean
|
|
55
|
-
defaultHours?: number
|
|
56
|
-
defaultMinutes?: number
|
|
57
|
-
defaultSeconds?: number
|
|
58
|
-
defaultMeridiem?: string
|
|
59
|
-
useSeconds?: boolean
|
|
60
|
-
}>()
|
|
61
|
-
|
|
62
|
-
// `source` is forwarded from ADateTimeInput, which declares it on every `get-time`. It is
|
|
63
|
-
// optional here only because a host or a test may emit these events by hand, and doing so always
|
|
64
|
-
// means a user action — `'init'` is the one case that has to be marked, because it is the widget
|
|
65
|
-
// announcing its start value as it mounts rather than anything the user did.
|
|
66
|
-
const emit = defineEmits<{
|
|
67
|
-
'get-date': [{ selected: Date; start?: Date | null; end?: Date | null }]
|
|
68
|
-
'get-time': [{ hours: number; minutes: number; seconds: number; meridiem: string; source?: 'init' | 'user' }]
|
|
69
|
-
'get-range': [{ start: Date; end: Date; source?: 'init' | 'user' }]
|
|
70
|
-
}>()
|
|
71
|
-
|
|
72
|
-
provide('select-range', selectRange)
|
|
73
|
-
|
|
74
|
-
const today = new Date()
|
|
75
|
-
const pickerStart = ref<Date>(today)
|
|
76
|
-
const pickerEnd = ref<Date>(today)
|
|
77
|
-
const startTimeMs = ref<number>(0)
|
|
78
|
-
const endTimeMs = ref<number>(0)
|
|
79
|
-
|
|
80
|
-
const mergeDateTime = (date: Date, timeMs: number): Date => {
|
|
81
|
-
const d = new Date(date)
|
|
82
|
-
d.setHours(0, 0, 0, 0)
|
|
83
|
-
return new Date(d.getTime() + timeMs)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const timePayloadToMs = (payload: {
|
|
87
|
-
hours: number
|
|
88
|
-
minutes: number
|
|
89
|
-
seconds: number
|
|
90
|
-
meridiem: string
|
|
91
|
-
militaryTime?: number
|
|
92
|
-
}): number => {
|
|
93
|
-
let h: number
|
|
94
|
-
if (payload.militaryTime !== undefined) {
|
|
95
|
-
h = payload.militaryTime
|
|
96
|
-
} else {
|
|
97
|
-
const hrs = payload.hours % 12
|
|
98
|
-
h = payload.meridiem === 'PM' ? hrs + 12 : hrs
|
|
99
|
-
}
|
|
100
|
-
return (h * 3600 + payload.minutes * 60 + (payload.seconds ?? 0)) * 1000
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const tryEmitRange = (source: 'init' | 'user') => {
|
|
104
|
-
if (!selectRange || !showTime || !showEndTime) return
|
|
105
|
-
emit('get-range', {
|
|
106
|
-
start: mergeDateTime(pickerStart.value, startTimeMs.value),
|
|
107
|
-
end: mergeDateTime(pickerEnd.value, endTimeMs.value),
|
|
108
|
-
source,
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const handleDate = (data: { start: Date | null; end: Date | null; selected: Date }) => {
|
|
113
|
-
emit('get-date', data)
|
|
114
|
-
if (selectRange) {
|
|
115
|
-
pickerStart.value = data.start ?? data.selected
|
|
116
|
-
pickerEnd.value = data.end ?? data.selected
|
|
117
|
-
// Picking a day on the calendar is unambiguously a user action.
|
|
118
|
-
tryEmitRange('user')
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const handleStartTime = (data: {
|
|
123
|
-
hours: number
|
|
124
|
-
minutes: number
|
|
125
|
-
seconds: number
|
|
126
|
-
meridiem: string
|
|
127
|
-
militaryTime?: number
|
|
128
|
-
source?: 'init' | 'user'
|
|
129
|
-
}) => {
|
|
130
|
-
startTimeMs.value = timePayloadToMs(data)
|
|
131
|
-
if (showEndTime) {
|
|
132
|
-
tryEmitRange(data.source ?? 'user')
|
|
133
|
-
} else {
|
|
134
|
-
emit('get-time', data)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const handleEndTime = (data: {
|
|
139
|
-
hours: number
|
|
140
|
-
minutes: number
|
|
141
|
-
seconds: number
|
|
142
|
-
meridiem: string
|
|
143
|
-
militaryTime?: number
|
|
144
|
-
source?: 'init' | 'user'
|
|
145
|
-
}) => {
|
|
146
|
-
endTimeMs.value = timePayloadToMs(data)
|
|
147
|
-
tryEmitRange(data.source ?? 'user')
|
|
148
|
-
}
|
|
149
|
-
</script>
|
|
150
|
-
|
|
151
|
-
<style scoped>
|
|
152
|
-
.adate-selection {
|
|
153
|
-
display: inline-block;
|
|
154
|
-
border: 1px solid var(--sc-gray-80);
|
|
155
|
-
padding: 10px;
|
|
156
|
-
background: var(--sc-form-background);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.adate-selection__end-label {
|
|
160
|
-
font-size: 0.8em;
|
|
161
|
-
font-weight: bold;
|
|
162
|
-
color: var(--sc-input-active-label-color);
|
|
163
|
-
margin-top: 8px;
|
|
164
|
-
margin-bottom: 2px;
|
|
165
|
-
padding-left: 2px;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.empty {
|
|
169
|
-
color: #ccc;
|
|
170
|
-
padding: 10px;
|
|
171
|
-
text-align: center;
|
|
172
|
-
border: 1px solid #ccc;
|
|
173
|
-
}
|
|
174
|
-
</style>
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="aform_form-element">
|
|
3
|
-
<template v-if="mode === 'display'">
|
|
4
|
-
<span class="aform_display-value">{{ displayValue }}</span>
|
|
5
|
-
<label class="aform_field-label">{{ label }}</label>
|
|
6
|
-
</template>
|
|
7
|
-
|
|
8
|
-
<template v-else>
|
|
9
|
-
<input
|
|
10
|
-
:id="uuid"
|
|
11
|
-
class="aform_input-field"
|
|
12
|
-
type="text"
|
|
13
|
-
:value="datetimeDisplay"
|
|
14
|
-
placeholder="Select date and time"
|
|
15
|
-
:disabled="mode === 'read'"
|
|
16
|
-
readonly
|
|
17
|
-
@click="openPicker" />
|
|
18
|
-
<label class="aform_field-label" :for="uuid">{{ label }}</label>
|
|
19
|
-
|
|
20
|
-
<p v-show="errorText" class="aform_error" v-html="errorText"></p>
|
|
21
|
-
|
|
22
|
-
<ADateSelection
|
|
23
|
-
v-if="showPicker"
|
|
24
|
-
ref="pickerRef"
|
|
25
|
-
class="adatetime-picker"
|
|
26
|
-
:select-range="false"
|
|
27
|
-
:show-date="true"
|
|
28
|
-
:show-time="true"
|
|
29
|
-
:default-hours="pickerDefaults.hours"
|
|
30
|
-
:default-minutes="pickerDefaults.minutes"
|
|
31
|
-
:default-seconds="pickerDefaults.seconds"
|
|
32
|
-
:default-meridiem="pickerDefaults.meridiem"
|
|
33
|
-
:allow-military-time="allowMilitaryTime"
|
|
34
|
-
:use-seconds="useSeconds"
|
|
35
|
-
@get-date="handleDate"
|
|
36
|
-
@get-time="handleTime" />
|
|
37
|
-
</template>
|
|
38
|
-
</div>
|
|
39
|
-
</template>
|
|
40
|
-
|
|
41
|
-
<script setup lang="ts">
|
|
42
|
-
import { ref, computed, watch } from 'vue'
|
|
43
|
-
import { onClickOutside } from '@vueuse/core'
|
|
44
|
-
import ADateSelection from './ADateSelection.vue'
|
|
45
|
-
import type { ComponentProps } from '../../types'
|
|
46
|
-
|
|
47
|
-
const {
|
|
48
|
-
label = 'Date & Time',
|
|
49
|
-
mode,
|
|
50
|
-
uuid,
|
|
51
|
-
errors,
|
|
52
|
-
validation = { errorMessage: '' },
|
|
53
|
-
allowMilitaryTime = false,
|
|
54
|
-
useSeconds = true,
|
|
55
|
-
} = defineProps<
|
|
56
|
-
ComponentProps & {
|
|
57
|
-
allowMilitaryTime?: boolean
|
|
58
|
-
useSeconds?: boolean
|
|
59
|
-
}
|
|
60
|
-
>()
|
|
61
|
-
|
|
62
|
-
const errorText = computed(() => (errors?.length ? errors.join('; ') : (validation.errorMessage ?? '')))
|
|
63
|
-
|
|
64
|
-
const modelValue = defineModel<string | Date>()
|
|
65
|
-
|
|
66
|
-
const currentDateTime = ref<Date>(modelValue.value ? new Date(modelValue.value) : new Date())
|
|
67
|
-
|
|
68
|
-
const showPicker = ref(false)
|
|
69
|
-
const pickerRef = ref(null)
|
|
70
|
-
onClickOutside(pickerRef, () => (showPicker.value = false))
|
|
71
|
-
|
|
72
|
-
const openPicker = () => {
|
|
73
|
-
if (mode !== 'read') showPicker.value = true
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const displayValue = computed(() => {
|
|
77
|
-
if (!modelValue.value) return ''
|
|
78
|
-
return currentDateTime.value.toLocaleString()
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const datetimeDisplay = computed(() => displayValue.value)
|
|
82
|
-
|
|
83
|
-
const pickerDefaults = computed(() => {
|
|
84
|
-
const d = currentDateTime.value
|
|
85
|
-
const hours24 = d.getHours()
|
|
86
|
-
const meridiem = hours24 >= 12 ? 'PM' : 'AM'
|
|
87
|
-
const hours12 = hours24 % 12 || 12
|
|
88
|
-
return {
|
|
89
|
-
hours: allowMilitaryTime ? hours24 : hours12,
|
|
90
|
-
minutes: d.getMinutes(),
|
|
91
|
-
seconds: d.getSeconds(),
|
|
92
|
-
meridiem,
|
|
93
|
-
}
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
const emitModel = () => {
|
|
97
|
-
modelValue.value = currentDateTime.value.toISOString()
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const handleDate = (data: { selected: Date }) => {
|
|
101
|
-
const next = new Date(currentDateTime.value)
|
|
102
|
-
next.setFullYear(data.selected.getFullYear(), data.selected.getMonth(), data.selected.getDate())
|
|
103
|
-
currentDateTime.value = next
|
|
104
|
-
emitModel()
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const handleTime = (data: {
|
|
108
|
-
hours: number
|
|
109
|
-
minutes: number
|
|
110
|
-
seconds: number
|
|
111
|
-
meridiem: string
|
|
112
|
-
militaryTime?: number
|
|
113
|
-
source?: 'init' | 'user'
|
|
114
|
-
}) => {
|
|
115
|
-
// The widget announces its own starting value as it mounts, and those defaults come from
|
|
116
|
-
// `pickerDefaults` — i.e. straight back out of this component. Writing that echo to the model
|
|
117
|
-
// meant one click on an empty field silently filled it with the current date and time.
|
|
118
|
-
if (data.source === 'init') return
|
|
119
|
-
|
|
120
|
-
const next = new Date(currentDateTime.value)
|
|
121
|
-
const hours = data.militaryTime ?? data.hours
|
|
122
|
-
next.setHours(hours, data.minutes, useSeconds ? data.seconds : 0, 0)
|
|
123
|
-
currentDateTime.value = next
|
|
124
|
-
emitModel()
|
|
125
|
-
// Deliberately does NOT close the picker. `get-time` is the widget's current value, not a
|
|
126
|
-
// commit — it fires on every blur, arrow key and meridiem change — so closing here shut the
|
|
127
|
-
// picker as soon as the user tabbed out of the hours field. Dismissal is the click-outside
|
|
128
|
-
// handler above; closing on `get-date` instead would strand the time half of a datetime.
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
watch(
|
|
132
|
-
() => modelValue.value,
|
|
133
|
-
newValue => {
|
|
134
|
-
if (newValue) {
|
|
135
|
-
currentDateTime.value = new Date(newValue)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
)
|
|
139
|
-
</script>
|
|
140
|
-
|
|
141
|
-
<style scoped>
|
|
142
|
-
.adatetime-picker {
|
|
143
|
-
position: absolute;
|
|
144
|
-
top: 100%;
|
|
145
|
-
left: 0;
|
|
146
|
-
z-index: 1000;
|
|
147
|
-
margin-top: 0.25rem;
|
|
148
|
-
}
|
|
149
|
-
</style>
|
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="adate_time">
|
|
3
|
-
<div class="adate_time_fields">
|
|
4
|
-
<input
|
|
5
|
-
v-model="timeData.hours"
|
|
6
|
-
type="text"
|
|
7
|
-
inputmode="numeric"
|
|
8
|
-
@paste="pasteInput($event, true)"
|
|
9
|
-
@focus="focusInput"
|
|
10
|
-
@blur="confirmTime"
|
|
11
|
-
@keydown.enter.prevent="confirmTime"
|
|
12
|
-
@keydown.up.prevent="tick('hours')"
|
|
13
|
-
@keydown.down.prevent="tick('hours', -1)" />
|
|
14
|
-
<span class="colon">:</span>
|
|
15
|
-
<input
|
|
16
|
-
v-model="timeData.minutes"
|
|
17
|
-
type="text"
|
|
18
|
-
inputmode="numeric"
|
|
19
|
-
@paste="pasteInput"
|
|
20
|
-
@focus="focusInput"
|
|
21
|
-
@blur="confirmTime"
|
|
22
|
-
@keydown.enter.prevent="confirmTime"
|
|
23
|
-
@keydown.up.prevent="tick('minutes')"
|
|
24
|
-
@keydown.down.prevent="tick('minutes', -1)" />
|
|
25
|
-
<span v-if="useSeconds" class="colon">:</span>
|
|
26
|
-
<input
|
|
27
|
-
v-if="useSeconds"
|
|
28
|
-
v-model="timeData.seconds"
|
|
29
|
-
type="text"
|
|
30
|
-
inputmode="numeric"
|
|
31
|
-
@paste="pasteInput"
|
|
32
|
-
@focus="focusInput"
|
|
33
|
-
@blur="confirmTime"
|
|
34
|
-
@keydown.enter.prevent="confirmTime"
|
|
35
|
-
@keydown.up.prevent="tick('seconds')"
|
|
36
|
-
@keydown.down.prevent="tick('seconds', -1)" />
|
|
37
|
-
<select
|
|
38
|
-
v-if="!allowMilitaryTime"
|
|
39
|
-
ref="meridiem-selector"
|
|
40
|
-
v-model="meridiem"
|
|
41
|
-
class="aform-select meridiem-selector"
|
|
42
|
-
@change="confirmTime">
|
|
43
|
-
<option value="AM">AM</option>
|
|
44
|
-
<option value="PM">PM</option>
|
|
45
|
-
</select>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</template>
|
|
49
|
-
|
|
50
|
-
<script setup lang="ts">
|
|
51
|
-
import { ref, reactive, useTemplateRef, watch, onMounted } from 'vue'
|
|
52
|
-
|
|
53
|
-
const {
|
|
54
|
-
allowMilitaryTime = false,
|
|
55
|
-
defaultHours = 12,
|
|
56
|
-
defaultMinutes = 0,
|
|
57
|
-
defaultSeconds = 0,
|
|
58
|
-
defaultMeridiem = 'AM',
|
|
59
|
-
useSeconds = true,
|
|
60
|
-
} = defineProps<{
|
|
61
|
-
allowMilitaryTime?: boolean
|
|
62
|
-
defaultHours?: number
|
|
63
|
-
defaultMinutes?: number
|
|
64
|
-
defaultSeconds?: number
|
|
65
|
-
defaultMeridiem?: string
|
|
66
|
-
useSeconds?: boolean
|
|
67
|
-
}>()
|
|
68
|
-
|
|
69
|
-
// `get-time` carries the widget's CURRENT VALUE, not a commit: it fires once as the widget
|
|
70
|
-
// mounts and again on every blur, Enter, arrow key and meridiem change — five times during one
|
|
71
|
-
// ordinary edit. `source` is what lets a parent tell the start-up announcement apart from a real
|
|
72
|
-
// user edit; without it a parent has nothing to key on and must guess from the value alone.
|
|
73
|
-
const emit = defineEmits<{
|
|
74
|
-
'get-time': [
|
|
75
|
-
{
|
|
76
|
-
hours: number
|
|
77
|
-
minutes: number
|
|
78
|
-
seconds: number
|
|
79
|
-
meridiem: string
|
|
80
|
-
militaryTime: number
|
|
81
|
-
source: 'init' | 'user'
|
|
82
|
-
},
|
|
83
|
-
]
|
|
84
|
-
}>()
|
|
85
|
-
|
|
86
|
-
const meridiemSelector = useTemplateRef<HTMLSelectElement>('meridiem-selector')
|
|
87
|
-
|
|
88
|
-
const timeData = reactive({
|
|
89
|
-
hours: String(defaultHours).padStart(2, '0'),
|
|
90
|
-
minutes: String(defaultMinutes).padStart(2, '0'),
|
|
91
|
-
seconds: String(defaultSeconds).padStart(2, '0'),
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
const meridiem = ref(defaultMeridiem == 'AM' ? 'AM' : 'PM')
|
|
95
|
-
|
|
96
|
-
onMounted(() => {
|
|
97
|
-
emitTime('init')
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const confirmTime = () => {
|
|
101
|
-
const maxHours = allowMilitaryTime ? 23 : 12
|
|
102
|
-
const minHours = allowMilitaryTime ? 0 : 1
|
|
103
|
-
let hours = Number(timeData.hours)
|
|
104
|
-
let minutes = Number(timeData.minutes)
|
|
105
|
-
let seconds = Number(timeData.seconds)
|
|
106
|
-
|
|
107
|
-
if (isNaN(hours) || timeData.hours === '' || hours > maxHours) hours = maxHours
|
|
108
|
-
if (!allowMilitaryTime && hours < minHours) hours = minHours
|
|
109
|
-
if (isNaN(minutes) || timeData.minutes === '' || minutes > 59) minutes = 59
|
|
110
|
-
if (isNaN(seconds) || timeData.seconds === '' || seconds > 59) seconds = 59
|
|
111
|
-
|
|
112
|
-
timeData.hours = String(hours).padStart(2, '0')
|
|
113
|
-
timeData.minutes = String(minutes).padStart(2, '0')
|
|
114
|
-
timeData.seconds = String(seconds).padStart(2, '0')
|
|
115
|
-
|
|
116
|
-
emitTime('user')
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const emitTime = (source: 'init' | 'user') => {
|
|
120
|
-
const hours = Number(timeData.hours)
|
|
121
|
-
const minutes = Number(timeData.minutes)
|
|
122
|
-
const seconds = Number(timeData.seconds)
|
|
123
|
-
emit('get-time', {
|
|
124
|
-
hours,
|
|
125
|
-
minutes,
|
|
126
|
-
seconds,
|
|
127
|
-
source,
|
|
128
|
-
meridiem: meridiem.value,
|
|
129
|
-
militaryTime: allowMilitaryTime ? hours : meridiem.value === 'PM' ? (hours === 12 ? 12 : hours + 12) : hours % 12,
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const focusInput = (event: FocusEvent) => {
|
|
134
|
-
const target = event.target
|
|
135
|
-
if (target instanceof HTMLInputElement) {
|
|
136
|
-
target.select()
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const tick = (target: 'hours' | 'minutes' | 'seconds', amount = 1) => {
|
|
141
|
-
const maxHours = allowMilitaryTime ? 23 : 12
|
|
142
|
-
const minHours = allowMilitaryTime ? 0 : 1
|
|
143
|
-
|
|
144
|
-
if (target == 'hours') {
|
|
145
|
-
const oldHours = Number(timeData.hours)
|
|
146
|
-
timeData.hours = String(oldHours + amount)
|
|
147
|
-
if ((oldHours == 11 && Number(timeData.hours) == 12) || (oldHours == 12 && Number(timeData.hours) == 11)) {
|
|
148
|
-
changeMeridiem()
|
|
149
|
-
}
|
|
150
|
-
} else if (target == 'minutes') {
|
|
151
|
-
timeData.minutes = String(Number(timeData.minutes) + amount)
|
|
152
|
-
} else if (target == 'seconds') {
|
|
153
|
-
timeData.seconds = String(Number(timeData.seconds) + amount)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const prevHours = Number(timeData.hours)
|
|
157
|
-
|
|
158
|
-
if (Number(timeData.seconds) < 0) timeData.minutes = String(Number(timeData.minutes) - 1)
|
|
159
|
-
else if (Number(timeData.seconds) > 59) timeData.minutes = String(Number(timeData.minutes) + 1)
|
|
160
|
-
|
|
161
|
-
if (Number(timeData.minutes) < 0) timeData.hours = String(prevHours - 1)
|
|
162
|
-
else if (Number(timeData.minutes) > 59) timeData.hours = String(prevHours + 1)
|
|
163
|
-
|
|
164
|
-
const newRawHours = Number(timeData.hours)
|
|
165
|
-
if (!allowMilitaryTime && newRawHours !== prevHours) {
|
|
166
|
-
if ((prevHours === 11 && newRawHours === 12) || (prevHours === 12 && newRawHours === 11)) {
|
|
167
|
-
changeMeridiem()
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
timeData.hours = String(formatTime(Number(timeData.hours), minHours, maxHours)).padStart(2, '0')
|
|
172
|
-
timeData.minutes = String(formatTime(Number(timeData.minutes), 0, 59)).padStart(2, '0')
|
|
173
|
-
timeData.seconds = String(formatTime(Number(timeData.seconds), 0, 59)).padStart(2, '0')
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
watch(
|
|
177
|
-
() => timeData.hours,
|
|
178
|
-
(newVal, oldVal) => {
|
|
179
|
-
timeData.hours = Number(newVal) > 99 ? oldVal : newVal
|
|
180
|
-
}
|
|
181
|
-
)
|
|
182
|
-
watch(
|
|
183
|
-
() => timeData.minutes,
|
|
184
|
-
(newVal, oldVal) => {
|
|
185
|
-
timeData.minutes = Number(newVal) > 99 ? oldVal : newVal
|
|
186
|
-
}
|
|
187
|
-
)
|
|
188
|
-
watch(
|
|
189
|
-
() => timeData.seconds,
|
|
190
|
-
(newVal, oldVal) => {
|
|
191
|
-
timeData.seconds = Number(newVal) > 99 ? oldVal : newVal
|
|
192
|
-
}
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
const formatTime = (target: number, min: number, max: number): number => {
|
|
196
|
-
if (target > max) return min
|
|
197
|
-
else if (target < min) return max
|
|
198
|
-
return target
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const changeMeridiem = () => {
|
|
202
|
-
meridiem.value = meridiem.value == 'PM' ? 'AM' : 'PM'
|
|
203
|
-
emitTime('user')
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const pasteInput = (event: ClipboardEvent, pasteAllFields = false) => {
|
|
207
|
-
event.stopPropagation()
|
|
208
|
-
event.preventDefault()
|
|
209
|
-
|
|
210
|
-
const clipboardData = event.clipboardData
|
|
211
|
-
if (!clipboardData) return
|
|
212
|
-
let pastedData: string = clipboardData.getData('Text')
|
|
213
|
-
|
|
214
|
-
pastedData = pastedData.replace(/[^0-9]/g, '')
|
|
215
|
-
|
|
216
|
-
if (pasteAllFields) {
|
|
217
|
-
if (pastedData.length % 2 != 0) pastedData = '0' + pastedData
|
|
218
|
-
if (pastedData.length < 3) pastedData += '00'
|
|
219
|
-
if (pastedData.length < 5) pastedData += '00'
|
|
220
|
-
|
|
221
|
-
const timeUnits = pastedData.match(/(..?)/g)
|
|
222
|
-
if (!timeUnits || timeUnits.length < 3) return
|
|
223
|
-
|
|
224
|
-
timeData.seconds = timeUnits[2]
|
|
225
|
-
timeData.minutes = timeUnits[1]
|
|
226
|
-
timeData.hours = timeUnits[0]
|
|
227
|
-
confirmTime()
|
|
228
|
-
if (!allowMilitaryTime) meridiemSelector.value?.focus()
|
|
229
|
-
} else {
|
|
230
|
-
if (pastedData.length > 2) pastedData = pastedData.slice(0, 2)
|
|
231
|
-
const target = event.target
|
|
232
|
-
if (target instanceof HTMLInputElement) {
|
|
233
|
-
target.value = pastedData
|
|
234
|
-
target.dispatchEvent(new Event('input'))
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
</script>
|
|
239
|
-
|
|
240
|
-
<style scoped>
|
|
241
|
-
.adate_time {
|
|
242
|
-
width: auto;
|
|
243
|
-
padding: 10px;
|
|
244
|
-
box-sizing: border-box;
|
|
245
|
-
font-size: 1rem;
|
|
246
|
-
background: var(--sc-gray-10);
|
|
247
|
-
}
|
|
248
|
-
.adate_time_fields {
|
|
249
|
-
display: flex;
|
|
250
|
-
align-items: stretch;
|
|
251
|
-
gap: 5px;
|
|
252
|
-
justify-content: flex-start;
|
|
253
|
-
}
|
|
254
|
-
.adate_time_fields > input {
|
|
255
|
-
min-width: 30px;
|
|
256
|
-
padding: 2px;
|
|
257
|
-
text-align: center;
|
|
258
|
-
display: inline-block;
|
|
259
|
-
flex-basis: 0;
|
|
260
|
-
}
|
|
261
|
-
.meridiem-selector {
|
|
262
|
-
cursor: pointer;
|
|
263
|
-
display: inline-block;
|
|
264
|
-
flex-basis: 0;
|
|
265
|
-
padding: 5px;
|
|
266
|
-
user-select: none;
|
|
267
|
-
}
|
|
268
|
-
.meridiem-selector:focus {
|
|
269
|
-
outline: 2px solid black;
|
|
270
|
-
outline-offset: -2px;
|
|
271
|
-
}
|
|
272
|
-
.adate_time_segment {
|
|
273
|
-
display: flex;
|
|
274
|
-
flex-direction: column;
|
|
275
|
-
width: 40px;
|
|
276
|
-
}
|
|
277
|
-
.colon {
|
|
278
|
-
display: flex;
|
|
279
|
-
align-items: normal;
|
|
280
|
-
}
|
|
281
|
-
.aform_form-btn {
|
|
282
|
-
cursor: pointer;
|
|
283
|
-
}
|
|
284
|
-
.aform-select {
|
|
285
|
-
border-radius: 0px;
|
|
286
|
-
border: 1px solid rgb(118, 118, 118);
|
|
287
|
-
font-size: 1rem;
|
|
288
|
-
padding: 0rem;
|
|
289
|
-
margin: 0;
|
|
290
|
-
border-radius: 0;
|
|
291
|
-
box-sizing: border-box;
|
|
292
|
-
min-height: auto;
|
|
293
|
-
position: relative;
|
|
294
|
-
color: var(--sc-cell-text-color);
|
|
295
|
-
}
|
|
296
|
-
.meridiem-selector {
|
|
297
|
-
margin-left: 6px;
|
|
298
|
-
}
|
|
299
|
-
</style>
|