@asd20/ui-next 2.25.3 → 2.26.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.26.1](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.26.0...ui-next-v2.26.1) (2026-09-30)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * show public form fallback only after submission failure ([7f17021](https://github.com/academydistrict20/asd20-ui-next/commit/7f17021d2e1f4e04fd193fb0e16d80125efe3ae7))
9
+
10
+ # [2.26.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.25.3...ui-next-v2.26.0) (2026-09-30)
11
+
12
+
13
+ ### Features
14
+
15
+ * add school transfer record request form with email workflow ([d92d458](https://github.com/academydistrict20/asd20-ui-next/commit/d92d458838303e7ae5ae600d26d6a6ce88fd748d))
16
+
3
17
  ## [2.25.3](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.25.2...ui-next-v2.25.3) (2026-09-25)
4
18
 
5
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.25.3",
3
+ "version": "2.26.1",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -43,6 +43,9 @@
43
43
 
44
44
  <form
45
45
  v-else-if="formDefinition"
46
+ :class="{
47
+ 'asd20-public-form-modal__records-form': usesRecordsFormSpacing,
48
+ }"
46
49
  novalidate
47
50
  @submit.prevent="validateBeforeCaptcha"
48
51
  >
@@ -52,6 +55,11 @@
52
55
  >
53
56
  {{ formDefinition.introduction }}
54
57
  </p>
58
+ <p v-if="formDefinition.introductionLink">
59
+ <a :href="formDefinition.introductionLink.href">
60
+ {{ formDefinition.introductionLink.label }}
61
+ </a>
62
+ </p>
55
63
 
56
64
  <div
57
65
  v-if="errorEntries.length"
@@ -77,6 +85,11 @@
77
85
  <asd20-public-form-field
78
86
  v-for="field in formDefinition.fields"
79
87
  :key="field.name"
88
+ :class="{
89
+ 'asd20-public-form-modal__requester-choice':
90
+ formDefinition.id === 'student-records-request' &&
91
+ field.name === 'requesterType',
92
+ }"
80
93
  :field="field"
81
94
  :input-id="fieldId(field.name)"
82
95
  :model-value="values[field.name]"
@@ -101,26 +114,6 @@
101
114
  />
102
115
  </div>
103
116
 
104
- <div
105
- v-if="hasFallbackContact"
106
- class="asd20-public-form-modal__fallback"
107
- >
108
- <p>
109
- {{
110
- formDefinition.fallbackContact.instructions ||
111
- 'If you cannot submit this form, use one of these contact options:'
112
- }}
113
- </p>
114
- <a
115
- v-if="formDefinition.fallbackContact.email"
116
- :href="`mailto:${formDefinition.fallbackContact.email}`"
117
- >{{ formDefinition.fallbackContact.email }}</a>
118
- <a
119
- v-if="formDefinition.fallbackContact.phone"
120
- :href="fallbackPhoneHref"
121
- >{{ formDefinition.fallbackContact.phone }}</a>
122
- </div>
123
-
124
117
  <div
125
118
  class="asd20-public-form-modal__submit"
126
119
  aria-live="polite"
@@ -167,6 +160,26 @@
167
160
  >
168
161
  {{ feedbackMessage }}
169
162
  </div>
163
+
164
+ <div
165
+ v-if="showFallbackContact"
166
+ class="asd20-public-form-modal__fallback"
167
+ >
168
+ <p>
169
+ {{
170
+ formDefinition.fallbackContact.instructions ||
171
+ 'If you cannot submit this form, use one of these contact options:'
172
+ }}
173
+ </p>
174
+ <a
175
+ v-if="formDefinition.fallbackContact.email"
176
+ :href="`mailto:${formDefinition.fallbackContact.email}`"
177
+ >{{ formDefinition.fallbackContact.email }}</a>
178
+ <a
179
+ v-if="formDefinition.fallbackContact.phone"
180
+ :href="fallbackPhoneHref"
181
+ >{{ formDefinition.fallbackContact.phone }}</a>
182
+ </div>
170
183
  </form>
171
184
  </asd20-viewport>
172
185
  </asd20-modal>
@@ -232,6 +245,7 @@ export default {
232
245
  feedbackRole: 'alert',
233
246
  sending: false,
234
247
  submissionSuccess: false,
248
+ submissionFailed: false,
235
249
  loadSequence: 0,
236
250
  }
237
251
  },
@@ -239,6 +253,12 @@ export default {
239
253
  modalTitle() {
240
254
  return this.formDefinition?.title || 'Public Form'
241
255
  },
256
+ usesRecordsFormSpacing() {
257
+ return [
258
+ 'student-records-request',
259
+ 'school-transfer-transcript-request',
260
+ ].includes(this.formDefinition?.id)
261
+ },
242
262
  showPristineValidationErrors() {
243
263
  return false
244
264
  },
@@ -253,7 +273,10 @@ export default {
253
273
  },
254
274
  hasFallbackContact() {
255
275
  const fallback = this.formDefinition?.fallbackContact
256
- return Boolean(fallback?.email || fallback?.phone)
276
+ return Boolean(fallback?.instructions || fallback?.email || fallback?.phone)
277
+ },
278
+ showFallbackContact() {
279
+ return this.submissionFailed && this.hasFallbackContact
257
280
  },
258
281
  fallbackPhoneHref() {
259
282
  const phone = this.formDefinition?.fallbackContact?.phone || ''
@@ -299,6 +322,7 @@ export default {
299
322
  this.definitionError = ''
300
323
  this.feedbackMessage = ''
301
324
  this.submissionSuccess = false
325
+ this.submissionFailed = false
302
326
  this.validationErrors = {}
303
327
  this.validationAttempted = false
304
328
  this.honeypot = ''
@@ -367,7 +391,7 @@ export default {
367
391
  },
368
392
  captchaFailed() {
369
393
  this.showFeedback(
370
- 'The verification service could not start. Please try again or use the contact options listed.'
394
+ 'The verification service could not start. Please try again or use another contact option on this page.'
371
395
  )
372
396
  },
373
397
  captchaExpired() {
@@ -407,6 +431,7 @@ export default {
407
431
 
408
432
  this.sending = true
409
433
  this.feedbackMessage = ''
434
+ this.submissionFailed = false
410
435
  try {
411
436
  const payload = {
412
437
  ...this.values,
@@ -424,6 +449,7 @@ export default {
424
449
  definitionVersion: this.formDefinition.version,
425
450
  })
426
451
  } catch (error) {
452
+ this.submissionFailed = true
427
453
  this.showFeedback(this.failureMessage(error))
428
454
  } finally {
429
455
  this.sending = false
@@ -453,6 +479,20 @@ export default {
453
479
  font-size: 1rem;
454
480
  }
455
481
 
482
+ &__records-form :deep(.asd20-text-input),
483
+ &__records-form :deep(.asd20-text-area-input) {
484
+ margin-bottom: 0.25rem;
485
+ }
486
+
487
+ &__records-form :deep(.asd20-public-form-field) {
488
+ margin-bottom: 1rem;
489
+ }
490
+
491
+ &__requester-choice.asd20-public-form-field
492
+ :deep(fieldset.asd20-public-form-field__choices label) {
493
+ align-items: flex-start;
494
+ }
495
+
456
496
  &__state,
457
497
  &__submit {
458
498
  align-items: center;
@@ -171,6 +171,25 @@ export function normalizePublicFormDefinition(definition) {
171
171
  const id = boundedText(definition.id, 'Form ID', 100, true)
172
172
  if (!FORM_ID_PATTERN.test(id)) throw new Error('The public form ID is invalid.')
173
173
 
174
+ let introductionLink
175
+ if (definition.introductionLink != null) {
176
+ const label = boundedText(
177
+ definition.introductionLink.label,
178
+ 'Introduction link label',
179
+ 200,
180
+ true
181
+ )
182
+ const href = boundedText(
183
+ definition.introductionLink.href,
184
+ 'Introduction link URL',
185
+ 500,
186
+ true
187
+ )
188
+ if (!/^\/[a-z0-9/-]+\/?$/.test(href) || href.startsWith('//'))
189
+ throw new Error('The introduction link must be a local page path.')
190
+ introductionLink = { label, href }
191
+ }
192
+
174
193
  return {
175
194
  id,
176
195
  version,
@@ -180,6 +199,7 @@ export function normalizePublicFormDefinition(definition) {
180
199
  'Form introduction',
181
200
  2000
182
201
  ),
202
+ introductionLink,
183
203
  submitLabel: boundedText(definition.submitLabel, 'Submit label', 100, true),
184
204
  successMessage: boundedText(
185
205
  definition.successMessage,