@stonecrop/aform 0.13.3 → 0.13.5

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.
@@ -0,0 +1,166 @@
1
+ <!-- This serves as a wrapper component for ADatePicker and ADateTime in one component -->
2
+
3
+ <template>
4
+ <div class="adate-selection">
5
+ <ADatePicker v-if="showDate" :select-range="selectRange" @get-date="handleDate" />
6
+
7
+ <ADateTime
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
+ <ADateTime
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 ADateTime from './ADateTime.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
+ const emit = defineEmits<{
63
+ 'get-date': [{ selected: Date; start?: Date | null; end?: Date | null }]
64
+ 'get-time': [{ hours: number; minutes: number; seconds: number; meridiem: string }]
65
+ 'get-range': [{ start: Date; end: Date }]
66
+ }>()
67
+
68
+ provide('select-range', selectRange)
69
+
70
+ const today = new Date()
71
+ const pickerStart = ref<Date>(today)
72
+ const pickerEnd = ref<Date>(today)
73
+ const startTimeMs = ref<number>(0)
74
+ const endTimeMs = ref<number>(0)
75
+
76
+ const mergeDateTime = (date: Date, timeMs: number): Date => {
77
+ const d = new Date(date)
78
+ d.setHours(0, 0, 0, 0)
79
+ return new Date(d.getTime() + timeMs)
80
+ }
81
+
82
+ const timePayloadToMs = (payload: {
83
+ hours: number
84
+ minutes: number
85
+ seconds: number
86
+ meridiem: string
87
+ militaryTime?: number
88
+ }): number => {
89
+ let h: number
90
+ if (payload.militaryTime !== undefined) {
91
+ h = payload.militaryTime
92
+ } else {
93
+ const hrs = payload.hours % 12
94
+ h = payload.meridiem === 'PM' ? hrs + 12 : hrs
95
+ }
96
+ return (h * 3600 + payload.minutes * 60 + (payload.seconds ?? 0)) * 1000
97
+ }
98
+
99
+ const tryEmitRange = () => {
100
+ if (!selectRange || !showTime || !showEndTime) return
101
+ emit('get-range', {
102
+ start: mergeDateTime(pickerStart.value, startTimeMs.value),
103
+ end: mergeDateTime(pickerEnd.value, endTimeMs.value),
104
+ })
105
+ }
106
+
107
+ const handleDate = (data: { start: Date | null; end: Date | null; selected: Date }) => {
108
+ emit('get-date', data)
109
+ if (selectRange) {
110
+ pickerStart.value = data.start ?? data.selected
111
+ pickerEnd.value = data.end ?? data.selected
112
+ tryEmitRange()
113
+ }
114
+ }
115
+
116
+ const handleStartTime = (data: {
117
+ hours: number
118
+ minutes: number
119
+ seconds: number
120
+ meridiem: string
121
+ militaryTime?: number
122
+ }) => {
123
+ startTimeMs.value = timePayloadToMs(data)
124
+ if (showEndTime) {
125
+ tryEmitRange()
126
+ } else {
127
+ emit('get-time', data)
128
+ }
129
+ }
130
+
131
+ const handleEndTime = (data: {
132
+ hours: number
133
+ minutes: number
134
+ seconds: number
135
+ meridiem: string
136
+ militaryTime?: number
137
+ }) => {
138
+ endTimeMs.value = timePayloadToMs(data)
139
+ tryEmitRange()
140
+ }
141
+ </script>
142
+
143
+ <style scoped>
144
+ .adate-selection {
145
+ display: inline-block;
146
+ border: 1px solid var(--sc-gray-80);
147
+ padding: 10px;
148
+ background: var(--sc-form-background);
149
+ }
150
+
151
+ .adate-selection__end-label {
152
+ font-size: 0.8em;
153
+ font-weight: bold;
154
+ color: var(--sc-input-active-label-color, #555);
155
+ margin-top: 8px;
156
+ margin-bottom: 2px;
157
+ padding-left: 2px;
158
+ }
159
+
160
+ .empty {
161
+ color: #ccc;
162
+ padding: 10px;
163
+ text-align: center;
164
+ border: 1px solid #ccc;
165
+ }
166
+ </style>
@@ -0,0 +1,285 @@
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
+ const emit = defineEmits<{
70
+ 'get-time': [{ hours: number; minutes: number; seconds: number; meridiem: string; militaryTime: number }]
71
+ }>()
72
+
73
+ const meridiemSelector = useTemplateRef<HTMLSelectElement>('meridiem-selector')
74
+
75
+ const timeData = reactive({
76
+ hours: String(defaultHours).padStart(2, '0'),
77
+ minutes: String(defaultMinutes).padStart(2, '0'),
78
+ seconds: String(defaultSeconds).padStart(2, '0'),
79
+ })
80
+
81
+ const meridiem = ref(defaultMeridiem == 'AM' ? 'AM' : 'PM')
82
+
83
+ onMounted(() => {
84
+ emitTime()
85
+ })
86
+
87
+ const confirmTime = () => {
88
+ const maxHours = allowMilitaryTime ? 23 : 12
89
+ const minHours = allowMilitaryTime ? 0 : 1
90
+ let hours = Number(timeData.hours)
91
+ let minutes = Number(timeData.minutes)
92
+ let seconds = Number(timeData.seconds)
93
+
94
+ if (isNaN(hours) || timeData.hours === '' || hours > maxHours) hours = maxHours
95
+ if (!allowMilitaryTime && hours < minHours) hours = minHours
96
+ if (isNaN(minutes) || timeData.minutes === '' || minutes > 59) minutes = 59
97
+ if (isNaN(seconds) || timeData.seconds === '' || seconds > 59) seconds = 59
98
+
99
+ timeData.hours = String(hours).padStart(2, '0')
100
+ timeData.minutes = String(minutes).padStart(2, '0')
101
+ timeData.seconds = String(seconds).padStart(2, '0')
102
+
103
+ emitTime()
104
+ }
105
+
106
+ const emitTime = () => {
107
+ const hours = Number(timeData.hours)
108
+ const minutes = Number(timeData.minutes)
109
+ const seconds = Number(timeData.seconds)
110
+ emit('get-time', {
111
+ hours,
112
+ minutes,
113
+ seconds,
114
+ meridiem: meridiem.value,
115
+ militaryTime: allowMilitaryTime ? hours : meridiem.value === 'PM' ? (hours === 12 ? 12 : hours + 12) : hours % 12,
116
+ })
117
+ }
118
+
119
+ const focusInput = (event: FocusEvent) => {
120
+ const target = event.target
121
+ if (target instanceof HTMLInputElement) {
122
+ target.select()
123
+ }
124
+ }
125
+
126
+ const tick = (target: 'hours' | 'minutes' | 'seconds', amount = 1) => {
127
+ const maxHours = allowMilitaryTime ? 23 : 12
128
+ const minHours = allowMilitaryTime ? 0 : 1
129
+
130
+ if (target == 'hours') {
131
+ const oldHours = Number(timeData.hours)
132
+ timeData.hours = String(oldHours + amount)
133
+ if ((oldHours == 11 && Number(timeData.hours) == 12) || (oldHours == 12 && Number(timeData.hours) == 11)) {
134
+ changeMeridiem()
135
+ }
136
+ } else if (target == 'minutes') {
137
+ timeData.minutes = String(Number(timeData.minutes) + amount)
138
+ } else if (target == 'seconds') {
139
+ timeData.seconds = String(Number(timeData.seconds) + amount)
140
+ }
141
+
142
+ const prevHours = Number(timeData.hours)
143
+
144
+ if (Number(timeData.seconds) < 0) timeData.minutes = String(Number(timeData.minutes) - 1)
145
+ else if (Number(timeData.seconds) > 59) timeData.minutes = String(Number(timeData.minutes) + 1)
146
+
147
+ if (Number(timeData.minutes) < 0) timeData.hours = String(prevHours - 1)
148
+ else if (Number(timeData.minutes) > 59) timeData.hours = String(prevHours + 1)
149
+
150
+ const newRawHours = Number(timeData.hours)
151
+ if (!allowMilitaryTime && newRawHours !== prevHours) {
152
+ if ((prevHours === 11 && newRawHours === 12) || (prevHours === 12 && newRawHours === 11)) {
153
+ changeMeridiem()
154
+ }
155
+ }
156
+
157
+ timeData.hours = String(formatTime(Number(timeData.hours), minHours, maxHours)).padStart(2, '0')
158
+ timeData.minutes = String(formatTime(Number(timeData.minutes), 0, 59)).padStart(2, '0')
159
+ timeData.seconds = String(formatTime(Number(timeData.seconds), 0, 59)).padStart(2, '0')
160
+ }
161
+
162
+ watch(
163
+ () => timeData.hours,
164
+ (newVal, oldVal) => {
165
+ timeData.hours = Number(newVal) > 99 ? oldVal : newVal
166
+ }
167
+ )
168
+ watch(
169
+ () => timeData.minutes,
170
+ (newVal, oldVal) => {
171
+ timeData.minutes = Number(newVal) > 99 ? oldVal : newVal
172
+ }
173
+ )
174
+ watch(
175
+ () => timeData.seconds,
176
+ (newVal, oldVal) => {
177
+ timeData.seconds = Number(newVal) > 99 ? oldVal : newVal
178
+ }
179
+ )
180
+
181
+ const formatTime = (target: number, min: number, max: number): number => {
182
+ if (target > max) return min
183
+ else if (target < min) return max
184
+ return target
185
+ }
186
+
187
+ const changeMeridiem = () => {
188
+ meridiem.value = meridiem.value == 'PM' ? 'AM' : 'PM'
189
+ emitTime()
190
+ }
191
+
192
+ const pasteInput = (event: ClipboardEvent, pasteAllFields = false) => {
193
+ event.stopPropagation()
194
+ event.preventDefault()
195
+
196
+ const clipboardData = event.clipboardData
197
+ if (!clipboardData) return
198
+ let pastedData: string = clipboardData.getData('Text')
199
+
200
+ pastedData = pastedData.replace(/[^0-9]/g, '')
201
+
202
+ if (pasteAllFields) {
203
+ if (pastedData.length % 2 != 0) pastedData = '0' + pastedData
204
+ if (pastedData.length < 3) pastedData += '00'
205
+ if (pastedData.length < 5) pastedData += '00'
206
+
207
+ const timeUnits = pastedData.match(/(..?)/g)
208
+ if (!timeUnits || timeUnits.length < 3) return
209
+
210
+ timeData.seconds = timeUnits[2]
211
+ timeData.minutes = timeUnits[1]
212
+ timeData.hours = timeUnits[0]
213
+ confirmTime()
214
+ if (!allowMilitaryTime) meridiemSelector.value?.focus()
215
+ } else {
216
+ if (pastedData.length > 2) pastedData = pastedData.slice(0, 2)
217
+ const target = event.target
218
+ if (target instanceof HTMLInputElement) {
219
+ target.value = pastedData
220
+ target.dispatchEvent(new Event('input'))
221
+ }
222
+ }
223
+ }
224
+ </script>
225
+
226
+ <style scoped>
227
+ .adate_time {
228
+ width: auto;
229
+ padding: 10px;
230
+ box-sizing: border-box;
231
+ font-size: 1rem;
232
+ background: var(--sc-gray-10);
233
+ }
234
+ .adate_time_fields {
235
+ display: flex;
236
+ align-items: stretch;
237
+ gap: 5px;
238
+ justify-content: flex-start;
239
+ }
240
+ .adate_time_fields > input {
241
+ min-width: 30px;
242
+ padding: 2px;
243
+ text-align: center;
244
+ display: inline-block;
245
+ flex-basis: 0;
246
+ }
247
+ .meridiem-selector {
248
+ cursor: pointer;
249
+ display: inline-block;
250
+ flex-basis: 0;
251
+ padding: 5px;
252
+ user-select: none;
253
+ }
254
+ .meridiem-selector:focus {
255
+ outline: 2px solid black;
256
+ outline-offset: -2px;
257
+ }
258
+ .adate_time_segment {
259
+ display: flex;
260
+ flex-direction: column;
261
+ width: 40px;
262
+ }
263
+ .colon {
264
+ display: flex;
265
+ align-items: normal;
266
+ }
267
+ .aform_form-btn {
268
+ cursor: pointer;
269
+ }
270
+ .aform-select {
271
+ border-radius: 0px;
272
+ border: 1px solid rgb(118, 118, 118);
273
+ font-size: 1rem;
274
+ padding: 0rem;
275
+ margin: 0;
276
+ border-radius: 0;
277
+ box-sizing: border-box;
278
+ min-height: auto;
279
+ position: relative;
280
+ color: var(--sc-cell-text-color);
281
+ }
282
+ .meridiem-selector {
283
+ margin-left: 6px;
284
+ }
285
+ </style>
@@ -0,0 +1,119 @@
1
+ <template>
2
+ <div class="aduration">
3
+ <template v-if="mode === 'display' || mode === 'read'">
4
+ <span class="aform_display-value">{{ displayValue }}</span>
5
+ <label>{{ label }}</label>
6
+ </template>
7
+
8
+ <template v-else>
9
+ <ADateSelection
10
+ ref="selectionRef"
11
+ :select-range="true"
12
+ :show-time="true"
13
+ :show-end-time="true"
14
+ :allow-military-time="allowMilitaryTime"
15
+ :use-seconds="useSeconds"
16
+ @get-range="handleRange" />
17
+ <div class="aduration__footer">
18
+ <label>{{ label }}</label>
19
+ <div v-if="startDatetime && endDatetime" class="aduration__summary">
20
+ <span class="aduration__label">Duration:</span>
21
+ <span class="aduration__value">{{ humanDuration }}</span>
22
+ <span class="aduration__ms">({{ modelValue ?? 0 }} ms)</span>
23
+ </div>
24
+ </div>
25
+ </template>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { ref, computed, watch } from 'vue'
31
+ import ADateSelection from './ADateSelection.vue'
32
+
33
+ const {
34
+ label = 'Duration',
35
+ mode = 'edit',
36
+ allowMilitaryTime = false,
37
+ useSeconds = false,
38
+ } = defineProps<{
39
+ label?: string
40
+ mode?: string
41
+ allowMilitaryTime?: boolean
42
+ useSeconds?: boolean
43
+ }>()
44
+
45
+ const modelValue = defineModel<number>()
46
+
47
+ const startDatetime = ref<Date | null>(null)
48
+ const endDatetime = ref<Date | null>(null)
49
+
50
+ const duration = computed<number>(() => {
51
+ if (!startDatetime.value || !endDatetime.value) return 0
52
+ const ms = endDatetime.value.getTime() - startDatetime.value.getTime()
53
+ return ms > 0 ? ms : 0
54
+ })
55
+
56
+ watch(duration, newMs => {
57
+ modelValue.value = newMs
58
+ })
59
+
60
+ const handleRange = (data: { start: Date; end: Date }) => {
61
+ startDatetime.value = data.start
62
+ endDatetime.value = data.end
63
+ modelValue.value = duration.value
64
+ }
65
+
66
+ const humanDuration = computed(() => {
67
+ const ms = duration.value
68
+ if (ms === 0) return '0s'
69
+ const s = Math.floor(ms / 1000) % 60
70
+ const m = Math.floor(ms / 60000) % 60
71
+ const h = Math.floor(ms / 3600000) % 24
72
+ const d = Math.floor(ms / 86400000)
73
+ return [d && `${d}d`, h && `${h}h`, m && `${m}m`, s && `${s}s`].filter(Boolean).join(' ') || '0s'
74
+ })
75
+
76
+ const displayValue = computed(() => {
77
+ const ms = modelValue.value
78
+ if (!ms) return '—'
79
+ const s = Math.floor(ms / 1000) % 60
80
+ const m = Math.floor(ms / 60000) % 60
81
+ const h = Math.floor(ms / 3600000) % 24
82
+ const d = Math.floor(ms / 86400000)
83
+ return [d && `${d}d`, h && `${h}h`, m && `${m}m`, s && `${s}s`].filter(Boolean).join(' ') || '0s'
84
+ })
85
+ </script>
86
+
87
+ <style scoped>
88
+ .aduration {
89
+ position: relative;
90
+ min-width: 40ch;
91
+ width: 100%;
92
+ }
93
+
94
+ .aduration__summary {
95
+ display: flex;
96
+ gap: 6px;
97
+ align-items: baseline;
98
+ margin-top: 6px;
99
+ font-size: 0.9em;
100
+ }
101
+
102
+ .aduration__label {
103
+ font-weight: bold;
104
+ color: var(--sc-input-active-label-color, #555);
105
+ }
106
+
107
+ .aduration__value {
108
+ color: var(--sc-cell-text-color, #000);
109
+ }
110
+
111
+ .aduration__ms {
112
+ color: var(--sc-gray-50, #888);
113
+ font-size: 0.85em;
114
+ }
115
+
116
+ .aduration__footer {
117
+ margin-top: 8px;
118
+ }
119
+ </style>
@@ -44,7 +44,7 @@ const fileLengthText = computed(() => {
44
44
  return `${count} ${count === 1 ? 'file' : 'files'}`
45
45
  })
46
46
 
47
- onChange(files => files)
47
+ onChange(changedFiles => changedFiles)
48
48
  </script>
49
49
 
50
50
  <style scoped>
@@ -122,7 +122,6 @@ watch(
122
122
  const results = await fn(String(id))
123
123
  displayText = results.find(r => String(r.id) === String(id))?.displayText
124
124
  if (displayText === undefined) {
125
- // eslint-disable-next-line no-console
126
125
  console.warn(
127
126
  `[AFormLink] filterFunction returned no matching result for id "${id}". ` +
128
127
  `The function must return an item whose \`id\` exactly matches the search string.`
@@ -146,7 +145,6 @@ const handleNavigate = () => {
146
145
  if (navigator && doctype) {
147
146
  navigator.navigate(doctype, modelValue.value.id)
148
147
  } else if (navigator && !doctype) {
149
- // eslint-disable-next-line no-console
150
148
  console.warn(
151
149
  `[AFormLink] Navigation requested but \`doctype\` prop is missing. ` +
152
150
  `Add \`options\` (or \`doctype\`) to the Link field definition to enable navigation.`
@@ -28,7 +28,11 @@ function getMask(binding: DirectiveBinding<string>) {
28
28
  if (maskFn) {
29
29
  // TODO: (state) replace with state management;
30
30
  // pass the entire form/table data to the function
31
- const locale = (binding.instance as Record<string, unknown> | null)?.['locale']
31
+ // Vue directive reads arbitrary fields off host component instance; type is unknown at this layer.
32
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
33
+ const instance = binding.instance as Record<string, unknown> | null | undefined
34
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
35
+ const locale = instance?.['locale'] as string | undefined
32
36
  return maskFn(locale)
33
37
  }
34
38
 
@@ -104,7 +108,9 @@ export function useStringMask(el: HTMLInputElement, binding: DirectiveBinding<st
104
108
  // most likely fixed with state management;
105
109
  // a better way could be to emit back to instance;
106
110
 
107
- const instance = binding.instance as Record<string, unknown> | null
111
+ // Vue directive reads/writes arbitrary fields on host component instance.
112
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
113
+ const instance = binding.instance as Record<string, unknown> | null | undefined
108
114
  if (instance?.['maskFilled']) {
109
115
  instance['maskFilled'] = !replacement.includes(maskToken)
110
116
  }
package/src/index.ts CHANGED
@@ -8,6 +8,10 @@ import AComboBox from './components/form/AComboBox.vue'
8
8
  import ADate from './components/form/ADate.vue'
9
9
  import ADropdown from './components/form/ADropdown.vue'
10
10
  import ADatePicker from './components/form/ADatePicker.vue'
11
+ import ADateTime from './components/form/ADateTime.vue'
12
+ import ADateSelection from './components/form/ADateSelection.vue'
13
+ import ADuration from './components/form/ADuration.vue'
14
+ import ADateRange from './components/form/ADateRange.vue'
11
15
  import AFieldset from './components/form/AFieldset.vue'
12
16
  import AFileAttach from './components/form/AFileAttach.vue'
13
17
  import AForm from './components/AForm.vue'
@@ -30,12 +34,16 @@ function install(app: App /* options */) {
30
34
  app.component('ADate', ADate)
31
35
  app.component('ADropdown', ADropdown)
32
36
  app.component('ADatePicker', ADatePicker)
37
+ app.component('ADateTime', ADateTime)
38
+ app.component('ADateRange', ADateRange)
39
+ app.component('ADateSelection', ADateSelection)
33
40
  app.component('AFieldset', AFieldset)
34
41
  app.component('AFileAttach', AFileAttach)
35
42
  app.component('AForm', AForm)
36
43
  app.component('AFormLink', AFormLink)
37
44
  app.component('ANumericInput', ANumericInput)
38
45
  app.component('ATextInput', ATextInput)
46
+ app.component('ADuration', ADuration)
39
47
  }
40
48
 
41
49
  export {
@@ -44,6 +52,10 @@ export {
44
52
  ADate,
45
53
  ADropdown,
46
54
  ADatePicker,
55
+ ADateRange,
56
+ ADateSelection,
57
+ ADuration,
58
+ ADateTime,
47
59
  AFieldset,
48
60
  AFileAttach,
49
61
  AForm,
@@ -24,6 +24,9 @@ export type ComponentProps = {
24
24
  */
25
25
  label?: string
26
26
 
27
+ // TODO: add docstring
28
+ selectRange?: boolean
29
+
27
30
  /**
28
31
  * The mask to apply to inputs inside the component. Accepts either a plain
29
32
  * mask string (e.g. `"(###) ###-####"`) or a stringified arrow function that
@@ -12,7 +12,9 @@
12
12
  * ```
13
13
  * @public
14
14
  */
15
+ // T is intentionally a caller-supplied return type — this utility is unsafe by design (Function constructor).
16
+ // oxlint-disable-next-line typescript/no-unnecessary-type-parameters
15
17
  export function deserializeFunction<T extends (...args: any[]) => any>(source: string): T {
16
- // eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-call
18
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion, typescript/no-implied-eval
17
19
  return Function(`"use strict"; return (${source})`)() as T
18
20
  }