@asd20/ui-next 2.24.4 → 2.25.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ # [2.25.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.24.4...ui-next-v2.25.0) (2026-09-21)
4
+
5
+
6
+ ### Features
7
+
8
+ * support public form date limits and fallback guidance ([4ef8551](https://github.com/academydistrict20/asd20-ui-next/commit/4ef8551aaa98cf66f48395a9bf98d68f107a0486))
9
+
3
10
  ## [2.24.4](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.24.3...ui-next-v2.24.4) (2026-09-17)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.24.4",
3
+ "version": "2.25.0",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -25,6 +25,7 @@
25
25
  :required="field.required"
26
26
  :minlength="field.minLength"
27
27
  :maxlength="field.maxLength"
28
+ :max="dateMaximum"
28
29
  :show-character-count="showCharacterCount"
29
30
  :show-pristine-validation-errors="showPristineValidationErrors"
30
31
  :autocomplete="field.autocomplete || undefined"
@@ -123,6 +124,19 @@ import Asd20Checkbox from '../../atoms/Asd20Checkbox'
123
124
  import Asd20TextAreaInput from '../Asd20TextAreaInput'
124
125
  import Asd20TextInput from '../Asd20TextInput'
125
126
 
127
+ function districtToday() {
128
+ const parts = new Intl.DateTimeFormat('en-US', {
129
+ timeZone: 'America/Denver',
130
+ year: 'numeric',
131
+ month: '2-digit',
132
+ day: '2-digit',
133
+ }).formatToParts(new Date())
134
+ const values = Object.fromEntries(
135
+ parts.map(({ type, value }) => [type, value])
136
+ )
137
+ return `${values.year}-${values.month}-${values.day}`
138
+ }
139
+
126
140
  export default {
127
141
  name: 'Asd20PublicFormField',
128
142
  components: { Asd20Checkbox, Asd20TextAreaInput, Asd20TextInput },
@@ -141,6 +155,11 @@ export default {
141
155
  }
142
156
  },
143
157
  computed: {
158
+ dateMaximum() {
159
+ return this.field.type === 'date' && this.field.maxDate === 'today'
160
+ ? districtToday()
161
+ : undefined
162
+ },
144
163
  displayLabel() {
145
164
  return this.field.required
146
165
  ? `${this.field.label} (required)`
@@ -102,7 +102,10 @@
102
102
  class="asd20-public-form-modal__fallback"
103
103
  >
104
104
  <p>
105
- If you cannot submit this form, use one of these contact options:
105
+ {{
106
+ formDefinition.fallbackContact.instructions ||
107
+ 'If you cannot submit this form, use one of these contact options:'
108
+ }}
106
109
  </p>
107
110
  <a
108
111
  v-if="formDefinition.fallbackContact.email"
@@ -34,6 +34,12 @@ function optionalLength(value, label) {
34
34
  return value
35
35
  }
36
36
 
37
+ function optionalMaxDate(value, type) {
38
+ if (value == null) return undefined
39
+ if (type === 'date' && value === 'today') return 'today'
40
+ throw new Error('Maximum date is invalid.')
41
+ }
42
+
37
43
  function normalizeOptions(field) {
38
44
  if (!['select', 'radio'].includes(field.type)) return undefined
39
45
  if (
@@ -81,6 +87,7 @@ function normalizeField(field) {
81
87
  required: field.required === true,
82
88
  minLength: optionalLength(field.minLength, 'Minimum length'),
83
89
  maxLength: optionalLength(field.maxLength, 'Maximum length'),
90
+ maxDate: optionalMaxDate(field.maxDate, type),
84
91
  autocomplete: boundedText(field.autocomplete, 'Autocomplete value', 100),
85
92
  options: undefined,
86
93
  }
@@ -134,9 +141,14 @@ export function normalizePublicFormDefinition(definition) {
134
141
  }
135
142
  const email = boundedText(fallbackContact.email, 'Fallback email', 320)
136
143
  const phone = boundedText(fallbackContact.phone, 'Fallback phone', 50)
144
+ const instructions = boundedText(
145
+ fallbackContact.instructions,
146
+ 'Fallback instructions',
147
+ 1000
148
+ )
137
149
  if (email && !EMAIL_PATTERN.test(email))
138
150
  throw new Error('The public form fallback email is invalid.')
139
- normalizedFallback = { email, phone }
151
+ normalizedFallback = { email, phone, instructions }
140
152
  }
141
153
 
142
154
  const id = boundedText(definition.id, 'Form ID', 100, true)
@@ -183,6 +195,19 @@ function validDate(value) {
183
195
  )
184
196
  }
185
197
 
198
+ function districtToday() {
199
+ const parts = new Intl.DateTimeFormat('en-US', {
200
+ timeZone: 'America/Denver',
201
+ year: 'numeric',
202
+ month: '2-digit',
203
+ day: '2-digit',
204
+ }).formatToParts(new Date())
205
+ const values = Object.fromEntries(
206
+ parts.map(({ type, value }) => [type, value])
207
+ )
208
+ return `${values.year}-${values.month}-${values.day}`
209
+ }
210
+
186
211
  export function validatePublicFormValues(definition, sourceValues = {}) {
187
212
  const errors = {}
188
213
  const values = {}
@@ -220,6 +245,14 @@ export function validatePublicFormValues(definition, sourceValues = {}) {
220
245
  if (value && field.type === 'date' && !validDate(value)) {
221
246
  fieldErrors.push(`Enter a valid ${field.label.toLowerCase()}.`)
222
247
  }
248
+ if (
249
+ value &&
250
+ field.type === 'date' &&
251
+ field.maxDate === 'today' &&
252
+ value > districtToday()
253
+ ) {
254
+ fieldErrors.push(`${field.label} cannot be in the future.`)
255
+ }
223
256
  if (value && ['select', 'radio'].includes(field.type)) {
224
257
  const allowedValues = new Set(
225
258
  (field.options || []).map(option => option.value)