@asd20/ui-next 2.24.4 → 2.25.1

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.25.1](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.25.0...ui-next-v2.25.1) (2026-09-23)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * add public form activation to sidebar links ([2f82e94](https://github.com/academydistrict20/asd20-ui-next/commit/2f82e94d2249df42f695f9cfe4fbbd4e254c46b7))
9
+
10
+ # [2.25.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.24.4...ui-next-v2.25.0) (2026-09-21)
11
+
12
+
13
+ ### Features
14
+
15
+ * support public form date limits and fallback guidance ([4ef8551](https://github.com/academydistrict20/asd20-ui-next/commit/4ef8551aaa98cf66f48395a9bf98d68f107a0486))
16
+
3
17
  ## [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
18
 
5
19
 
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.1",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -3,6 +3,8 @@
3
3
  :is="tag"
4
4
  :[hrefAttr]="hrefValue"
5
5
  :[targetAttr]="targetValue"
6
+ :type="tag === 'button' ? 'button' : undefined"
7
+ :data-public-form="normalizedPublicFormId || undefined"
6
8
  class="asd20-list-item"
7
9
  :class="classes"
8
10
  @click="onClick"
@@ -168,6 +170,7 @@ export default {
168
170
  alignTop: { type: Boolean, default: false },
169
171
  to: { type: [String, Object], default: '' },
170
172
  link: { type: String, default: '' },
173
+ publicFormId: { type: String, default: '' },
171
174
  sourceTitle: { type: String, default: '' },
172
175
  logoUrl: { type: String, default: '' },
173
176
  attribution: { type: Boolean, default: false },
@@ -186,6 +189,9 @@ export default {
186
189
  }
187
190
  },
188
191
  tag() {
192
+ if (this.actionable && this.normalizedPublicFormId) {
193
+ return 'button'
194
+ }
189
195
  if (this.actionable && this.usesRouterNavigation) {
190
196
  return 'button'
191
197
  }
@@ -201,8 +207,14 @@ export default {
201
207
  return this.tag === 'a' ? 'href' : null
202
208
  },
203
209
  hrefValue() {
210
+ if (this.normalizedPublicFormId) return ''
204
211
  return this.link || this.to
205
212
  },
213
+ normalizedPublicFormId() {
214
+ if (!this.actionable) return ''
215
+ const formId = String(this.publicFormId || '').trim()
216
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(formId) ? formId : ''
217
+ },
206
218
  usesRouterNavigation() {
207
219
  const router = resolveRouter(this)
208
220
  if (!router || !this.to) return false
@@ -230,7 +242,12 @@ export default {
230
242
  onClick(e) {
231
243
  const router = resolveRouter(this)
232
244
 
233
- if (this.usesRouterNavigation && router && typeof router.push === 'function') {
245
+ if (
246
+ !this.normalizedPublicFormId &&
247
+ this.usesRouterNavigation &&
248
+ router &&
249
+ typeof router.push === 'function'
250
+ ) {
234
251
  e.preventDefault()
235
252
  router.push(this.to)
236
253
  }
@@ -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"
@@ -14,6 +14,7 @@ export default function mapLinksToListItems(links) {
14
14
  alignTop: false,
15
15
  tags: [],
16
16
  link: l.url || '',
17
+ publicFormId: l.publicFormId || '',
17
18
  thumbnail: l.imageUrl,
18
19
  to: l.to || l.path,
19
20
  }
@@ -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)