@asd20/ui-next 2.35.1 → 2.35.3
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.35.3](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.35.2...ui-next-v2.35.3) (2026-11-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* give transcript requests a prominent alternate path in the student records form ([1250f67](https://github.com/academydistrict20/asd20-ui-next/commit/1250f6773bfa0047a234ea064463408cf4e51713))
|
|
9
|
+
|
|
10
|
+
## [2.35.2](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.35.1...ui-next-v2.35.2) (2026-11-04)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* restore previous sidebar scroll behavior for handbook template ([ff72930](https://github.com/academydistrict20/asd20-ui-next/commit/ff729302a2596d87928aee3a419e345d841fa910))
|
|
16
|
+
|
|
3
17
|
## [2.35.1](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.35.0...ui-next-v2.35.1) (2026-11-04)
|
|
4
18
|
|
|
5
19
|
|
package/package.json
CHANGED
|
@@ -49,8 +49,37 @@
|
|
|
49
49
|
novalidate
|
|
50
50
|
@submit.prevent="validateBeforeCaptcha"
|
|
51
51
|
>
|
|
52
|
+
<asd20-public-form-field
|
|
53
|
+
v-if="requestTypeField"
|
|
54
|
+
class="asd20-public-form-modal__requester-choice asd20-public-form-modal__records-choice"
|
|
55
|
+
:field="requestTypeField"
|
|
56
|
+
:input-id="fieldId('requesterType')"
|
|
57
|
+
:model-value="values.requesterType"
|
|
58
|
+
:errors="validationErrors.requesterType || []"
|
|
59
|
+
:show-pristine-validation-errors="false"
|
|
60
|
+
@update:modelValue="updateValue('requesterType', $event)"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<section v-if="isStudentRecordsForm && values.requesterType === 'transcriptOnly'">
|
|
64
|
+
<h3>Request a Transcript</h3>
|
|
65
|
+
<p>
|
|
66
|
+
For transcripts, graduation verification or replacement diplomas,
|
|
67
|
+
please use our dedicated Transcripts and Diplomas page.
|
|
68
|
+
</p>
|
|
69
|
+
<p>
|
|
70
|
+
<a :href="transcriptsHref">Go to Transcripts and Diplomas</a>
|
|
71
|
+
</p>
|
|
72
|
+
</section>
|
|
73
|
+
|
|
74
|
+
<template v-if="isStudentRecordsForm && showRequestFields">
|
|
75
|
+
<h3>{{ recordsRequestTitle }}</h3>
|
|
76
|
+
<p v-if="requestTypeDescription">
|
|
77
|
+
{{ requestTypeDescription }}
|
|
78
|
+
</p>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
52
81
|
<p
|
|
53
|
-
v-if="formDefinition.introduction"
|
|
82
|
+
v-if="showRequestFields && formDefinition.introduction"
|
|
54
83
|
class="asd20-public-form-modal__introduction"
|
|
55
84
|
>
|
|
56
85
|
<template v-if="inlineIntroductionParts">
|
|
@@ -65,7 +94,7 @@
|
|
|
65
94
|
</p>
|
|
66
95
|
<p
|
|
67
96
|
v-if="
|
|
68
|
-
formDefinition.introductionLink &&
|
|
97
|
+
showRequestFields && formDefinition.introductionLink &&
|
|
69
98
|
!formDefinition.introductionLink.inline
|
|
70
99
|
"
|
|
71
100
|
>
|
|
@@ -128,6 +157,7 @@
|
|
|
128
157
|
</div>
|
|
129
158
|
|
|
130
159
|
<div
|
|
160
|
+
v-if="showRequestFields"
|
|
131
161
|
class="asd20-public-form-modal__submit"
|
|
132
162
|
aria-live="polite"
|
|
133
163
|
>
|
|
@@ -197,6 +227,29 @@
|
|
|
197
227
|
</div>
|
|
198
228
|
</form>
|
|
199
229
|
</asd20-viewport>
|
|
230
|
+
|
|
231
|
+
<asd20-modal
|
|
232
|
+
:open="open && showTranscriptReminder"
|
|
233
|
+
title="Transcript-only request"
|
|
234
|
+
dismissable
|
|
235
|
+
@dismiss="showTranscriptReminder = false"
|
|
236
|
+
>
|
|
237
|
+
<asd20-viewport scrollable>
|
|
238
|
+
<p>
|
|
239
|
+
You selected only a transcript. Most transcript-only requests should be
|
|
240
|
+
submitted through our Transcripts and Diplomas page.
|
|
241
|
+
</p>
|
|
242
|
+
<p>
|
|
243
|
+
<a :href="transcriptsHref">Go to Transcripts and Diplomas</a>
|
|
244
|
+
</p>
|
|
245
|
+
<asd20-button
|
|
246
|
+
label="Back to request"
|
|
247
|
+
horizontal
|
|
248
|
+
bordered
|
|
249
|
+
@click="showTranscriptReminder = false"
|
|
250
|
+
/>
|
|
251
|
+
</asd20-viewport>
|
|
252
|
+
</asd20-modal>
|
|
200
253
|
</asd20-modal>
|
|
201
254
|
</template>
|
|
202
255
|
|
|
@@ -221,6 +274,11 @@ import {
|
|
|
221
274
|
submitPublicForm,
|
|
222
275
|
} from '../../../helpers/publicForms'
|
|
223
276
|
import createComponentInstanceId from '../../../utils/createComponentInstanceId'
|
|
277
|
+
import {
|
|
278
|
+
studentRecordsPresentation,
|
|
279
|
+
studentRecordsSubmissionValues,
|
|
280
|
+
usesStudentRecordsRouting,
|
|
281
|
+
} from '../../../helpers/studentRecordsRequest'
|
|
224
282
|
|
|
225
283
|
const DEFAULT_CAPTCHA_SITEKEY = '6LfidKoUAAAAAFqr3QEbia3jIkecsZyxBYlMvWrX'
|
|
226
284
|
|
|
@@ -253,6 +311,9 @@ export default {
|
|
|
253
311
|
instanceId,
|
|
254
312
|
honeypotId: `${instanceId}-website`,
|
|
255
313
|
formDefinition: null,
|
|
314
|
+
serviceDefinition: null,
|
|
315
|
+
transcriptsHref: 'https://www.asd20.org/transcripts-and-diplomas/',
|
|
316
|
+
showTranscriptReminder: false,
|
|
256
317
|
values: {},
|
|
257
318
|
honeypot: '',
|
|
258
319
|
validationErrors: {},
|
|
@@ -271,12 +332,15 @@ export default {
|
|
|
271
332
|
canSubmit() {
|
|
272
333
|
return Boolean(
|
|
273
334
|
this.formDefinition &&
|
|
335
|
+
this.showRequestFields &&
|
|
274
336
|
!this.sending &&
|
|
275
337
|
validatePublicFormValues(this.formDefinition, this.values).valid
|
|
276
338
|
)
|
|
277
339
|
},
|
|
278
340
|
visibleFields() {
|
|
279
341
|
return (this.formDefinition?.fields || [])
|
|
342
|
+
.filter(field => !this.isStudentRecordsForm ||
|
|
343
|
+
(this.showRequestFields && field.name !== 'requesterType'))
|
|
280
344
|
.filter(field => isPublicFormFieldVisible(field, this.values))
|
|
281
345
|
.map(field => ({
|
|
282
346
|
...field,
|
|
@@ -286,6 +350,27 @@ export default {
|
|
|
286
350
|
modalTitle() {
|
|
287
351
|
return this.formDefinition?.title || 'Public Form'
|
|
288
352
|
},
|
|
353
|
+
isStudentRecordsForm() {
|
|
354
|
+
return usesStudentRecordsRouting(this.formDefinition)
|
|
355
|
+
},
|
|
356
|
+
requestTypeField() {
|
|
357
|
+
return this.isStudentRecordsForm
|
|
358
|
+
? this.formDefinition.fields.find(field => field.name === 'requesterType')
|
|
359
|
+
: null
|
|
360
|
+
},
|
|
361
|
+
showRequestFields() {
|
|
362
|
+
return !this.isStudentRecordsForm ||
|
|
363
|
+
['schoolTransfer', 'studentRecords'].includes(this.values.requesterType)
|
|
364
|
+
},
|
|
365
|
+
recordsRequestTitle() {
|
|
366
|
+
return this.values.requesterType === 'schoolTransfer'
|
|
367
|
+
? 'School Transfer Records Request'
|
|
368
|
+
: 'Student Records Request (FERPA Requests Only)'
|
|
369
|
+
},
|
|
370
|
+
requestTypeDescription() {
|
|
371
|
+
return this.serviceDefinition?.fields.find(field => field.name === 'requesterType')
|
|
372
|
+
?.options.find(option => option.value === this.values.requesterType)?.description
|
|
373
|
+
},
|
|
289
374
|
inlineIntroductionParts() {
|
|
290
375
|
const introduction = this.formDefinition?.introduction || ''
|
|
291
376
|
const link = this.formDefinition?.introductionLink
|
|
@@ -371,6 +456,7 @@ export default {
|
|
|
371
456
|
this.validationErrors = {}
|
|
372
457
|
this.validationAttempted = false
|
|
373
458
|
this.honeypot = ''
|
|
459
|
+
this.showTranscriptReminder = false
|
|
374
460
|
|
|
375
461
|
try {
|
|
376
462
|
const expectedId = normalizeFormId(this.formId)
|
|
@@ -387,11 +473,13 @@ export default {
|
|
|
387
473
|
)
|
|
388
474
|
if (sequence !== this.loadSequence) return
|
|
389
475
|
|
|
390
|
-
this.
|
|
391
|
-
this.
|
|
476
|
+
this.serviceDefinition = normalized
|
|
477
|
+
this.formDefinition = studentRecordsPresentation(normalized)
|
|
478
|
+
this.values = createPublicFormValues(this.formDefinition)
|
|
392
479
|
} catch (_error) {
|
|
393
480
|
if (sequence !== this.loadSequence) return
|
|
394
481
|
this.formDefinition = null
|
|
482
|
+
this.serviceDefinition = null
|
|
395
483
|
this.definitionError =
|
|
396
484
|
'This form could not be loaded. Please try again later or use another contact option on this page.'
|
|
397
485
|
this.$nextTick(() => this.$refs.definitionError?.focus?.())
|
|
@@ -406,6 +494,15 @@ export default {
|
|
|
406
494
|
return field.showCharacterCount === true
|
|
407
495
|
},
|
|
408
496
|
updateValue(name, value) {
|
|
497
|
+
if (this.isStudentRecordsForm && name === 'requesterType' && this.values[name] !== value) {
|
|
498
|
+
this.values.transferRecordTypes = []
|
|
499
|
+
this.values.otherRecordsDescription = ''
|
|
500
|
+
this.showTranscriptReminder = false
|
|
501
|
+
this.validationErrors = {}
|
|
502
|
+
this.validationAttempted = false
|
|
503
|
+
this.feedbackMessage = ''
|
|
504
|
+
this.submissionFailed = false
|
|
505
|
+
}
|
|
409
506
|
this.values[name] = value
|
|
410
507
|
for (const field of this.formDefinition.fields) {
|
|
411
508
|
if (!isPublicFormFieldVisible(field, this.values)) {
|
|
@@ -416,7 +513,7 @@ export default {
|
|
|
416
513
|
if (this.validationAttempted) this.validate(false)
|
|
417
514
|
},
|
|
418
515
|
validate(focusSummary = true) {
|
|
419
|
-
if (!this.formDefinition) return false
|
|
516
|
+
if (!this.formDefinition || !this.showRequestFields) return false
|
|
420
517
|
const result = validatePublicFormValues(this.formDefinition, this.values)
|
|
421
518
|
this.values = result.values
|
|
422
519
|
this.validationErrors = result.errors
|
|
@@ -428,12 +525,21 @@ export default {
|
|
|
428
525
|
return result.valid
|
|
429
526
|
},
|
|
430
527
|
validateBeforeCaptcha(event) {
|
|
431
|
-
if (this.sending || !this.validate()) {
|
|
528
|
+
if (this.sending || !this.validate() || this.checkTranscriptOnly()) {
|
|
432
529
|
event?.preventDefault?.()
|
|
433
530
|
return false
|
|
434
531
|
}
|
|
435
532
|
return true
|
|
436
533
|
},
|
|
534
|
+
checkTranscriptOnly() {
|
|
535
|
+
if (this.isStudentRecordsForm &&
|
|
536
|
+
this.values.transferRecordTypes.length === 1 &&
|
|
537
|
+
this.values.transferRecordTypes[0] === 'academicRecords') {
|
|
538
|
+
this.showTranscriptReminder = true
|
|
539
|
+
return true
|
|
540
|
+
}
|
|
541
|
+
return false
|
|
542
|
+
},
|
|
437
543
|
focusField(name) {
|
|
438
544
|
document.getElementById(this.fieldId(name))?.focus?.()
|
|
439
545
|
},
|
|
@@ -480,7 +586,7 @@ export default {
|
|
|
480
586
|
this.$nextTick(() => this.$refs.feedback?.focus?.())
|
|
481
587
|
},
|
|
482
588
|
async submitForm(captchaToken = '') {
|
|
483
|
-
if (this.sending || !this.validate()) return
|
|
589
|
+
if (this.sending || !this.validate() || this.checkTranscriptOnly()) return
|
|
484
590
|
if (this.captchaEnabled && !captchaToken) {
|
|
485
591
|
this.showFeedback(
|
|
486
592
|
'Verification is required before submitting this form.'
|
|
@@ -494,8 +600,11 @@ export default {
|
|
|
494
600
|
try {
|
|
495
601
|
const payload = {
|
|
496
602
|
...(await serializePublicFormValues(
|
|
497
|
-
this.
|
|
498
|
-
|
|
603
|
+
this.serviceDefinition,
|
|
604
|
+
validatePublicFormValues(
|
|
605
|
+
this.serviceDefinition,
|
|
606
|
+
studentRecordsSubmissionValues(this.serviceDefinition, this.values)
|
|
607
|
+
).values
|
|
499
608
|
)),
|
|
500
609
|
website: this.honeypot,
|
|
501
610
|
captchaToken,
|
|
@@ -555,6 +664,12 @@ export default {
|
|
|
555
664
|
align-items: flex-start;
|
|
556
665
|
}
|
|
557
666
|
|
|
667
|
+
&__records-choice :deep(legend) {
|
|
668
|
+
font-size: 1.5rem;
|
|
669
|
+
font-weight: 700;
|
|
670
|
+
margin-bottom: 0.5rem;
|
|
671
|
+
}
|
|
672
|
+
|
|
558
673
|
&__state,
|
|
559
674
|
&__submit {
|
|
560
675
|
align-items: center;
|
|
@@ -142,9 +142,7 @@
|
|
|
142
142
|
</p>
|
|
143
143
|
|
|
144
144
|
<nav
|
|
145
|
-
ref="topicIndex"
|
|
146
145
|
class="asd20-template-handbook__index"
|
|
147
|
-
:class="{ 'asd20-template-handbook__index--sticky': indexFitsViewport }"
|
|
148
146
|
aria-label="Handbook topics"
|
|
149
147
|
>
|
|
150
148
|
<slot name="sidebar-status" />
|
|
@@ -325,7 +323,6 @@ export default {
|
|
|
325
323
|
topicsOpen: false,
|
|
326
324
|
handbookHelpOpen: false,
|
|
327
325
|
fragmentEpoch: 0,
|
|
328
|
-
indexFitsViewport: false,
|
|
329
326
|
}),
|
|
330
327
|
computed: {
|
|
331
328
|
handbookDocument() {
|
|
@@ -361,34 +358,19 @@ export default {
|
|
|
361
358
|
},
|
|
362
359
|
mounted() {
|
|
363
360
|
this.updatePrintLifecycle()
|
|
364
|
-
if (typeof window.ResizeObserver === 'function') {
|
|
365
|
-
this.indexResizeObserver = new window.ResizeObserver(this.updateIndexFit)
|
|
366
|
-
this.indexResizeObserver.observe(this.$refs.topicIndex)
|
|
367
|
-
this.updateIndexFit()
|
|
368
|
-
}
|
|
369
|
-
window.addEventListener('resize', this.updateIndexFit)
|
|
370
361
|
window.addEventListener('hashchange', this.onFragmentChange)
|
|
371
362
|
window.addEventListener('popstate', this.onFragmentChange)
|
|
372
363
|
this.onFragmentChange()
|
|
373
364
|
},
|
|
374
365
|
beforeUnmount() {
|
|
375
366
|
this.fragmentEpoch++
|
|
376
|
-
this.indexResizeObserver?.disconnect()
|
|
377
|
-
window.removeEventListener('resize', this.updateIndexFit)
|
|
378
367
|
window.removeEventListener('hashchange', this.onFragmentChange)
|
|
379
368
|
window.removeEventListener('popstate', this.onFragmentChange)
|
|
380
369
|
this.stopPrintLifecycle?.()
|
|
381
370
|
},
|
|
382
371
|
methods: {
|
|
383
|
-
updateIndexFit() {
|
|
384
|
-
const index = this.$refs.topicIndex
|
|
385
|
-
if (!index || !this.indexResizeObserver) return
|
|
386
|
-
const view = index.ownerDocument.defaultView
|
|
387
|
-
const inset = parseFloat(view.getComputedStyle(index).top) || 0
|
|
388
|
-
this.indexFitsViewport = index.getBoundingClientRect().height <= view.innerHeight - inset * 2
|
|
389
|
-
},
|
|
390
372
|
closeTopics() {
|
|
391
|
-
const index = this.$
|
|
373
|
+
const index = this.$el.querySelector('.asd20-template-handbook__index')
|
|
392
374
|
const focusContent = this.topicsOpen && index.contains(index.ownerDocument.activeElement)
|
|
393
375
|
this.topicsOpen = false
|
|
394
376
|
return focusContent
|
|
@@ -436,6 +418,7 @@ export default {
|
|
|
436
418
|
}
|
|
437
419
|
const epoch = ++this.fragmentEpoch
|
|
438
420
|
this.selectedMessageId = entry.key
|
|
421
|
+
this.expandedTopicId = entry.key
|
|
439
422
|
this.topicsOpen = false
|
|
440
423
|
await this.$nextTick()
|
|
441
424
|
await this.$nextTick()
|
|
@@ -496,6 +479,7 @@ export default {
|
|
|
496
479
|
async selectTopic(messageId) {
|
|
497
480
|
const epoch = ++this.fragmentEpoch
|
|
498
481
|
this.selectedMessageId = messageId
|
|
482
|
+
this.expandedTopicId = this.subtopicsFor(messageId).length ? messageId : null
|
|
499
483
|
const focusContent = this.closeTopics()
|
|
500
484
|
await this.$nextTick()
|
|
501
485
|
if (epoch !== this.fragmentEpoch) return
|
|
@@ -709,12 +693,11 @@ export default {
|
|
|
709
693
|
}
|
|
710
694
|
|
|
711
695
|
&__index {
|
|
696
|
+
position: sticky;
|
|
712
697
|
top: space(2);
|
|
713
698
|
align-self: start;
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
&__index--sticky {
|
|
717
|
-
position: sticky;
|
|
699
|
+
max-height: calc(100vh - 4rem);
|
|
700
|
+
overflow-y: auto;
|
|
718
701
|
}
|
|
719
702
|
|
|
720
703
|
&__index-heading,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Presentation for the unified form; the service still accepts its v8 wire fields.
|
|
2
|
+
export function usesStudentRecordsRouting(definition) {
|
|
3
|
+
return (
|
|
4
|
+
definition?.id === 'student-records-request' &&
|
|
5
|
+
definition.fields.some(field =>
|
|
6
|
+
field.name === 'requesterType' &&
|
|
7
|
+
field.options?.some(option => option.value === 'schoolTransfer')
|
|
8
|
+
) &&
|
|
9
|
+
definition.fields.some(field => field.name === 'transferRecordTypes')
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function studentRecordsPresentation(definition) {
|
|
14
|
+
if (!usesStudentRecordsRouting(definition)) return definition
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
...definition,
|
|
18
|
+
fields: definition.fields
|
|
19
|
+
.filter(field => field.name !== 'recordsRequested')
|
|
20
|
+
.map(field => {
|
|
21
|
+
if (field.name === 'requesterType') {
|
|
22
|
+
return {
|
|
23
|
+
...field,
|
|
24
|
+
label: 'What records do you need?',
|
|
25
|
+
options: [
|
|
26
|
+
{
|
|
27
|
+
label: 'Transcript or graduation verification only',
|
|
28
|
+
value: 'transcriptOnly',
|
|
29
|
+
},
|
|
30
|
+
{ label: 'School transfer records', value: 'schoolTransfer' },
|
|
31
|
+
{ label: 'Other student records', value: 'studentRecords' },
|
|
32
|
+
],
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (field.name === 'transferRecordTypes') {
|
|
36
|
+
return {
|
|
37
|
+
...field,
|
|
38
|
+
visibleWhen: {
|
|
39
|
+
fieldName: 'requesterType',
|
|
40
|
+
values: ['schoolTransfer', 'studentRecords'],
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return field
|
|
45
|
+
}),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function studentRecordsSubmissionValues(definition, values) {
|
|
50
|
+
if (
|
|
51
|
+
!usesStudentRecordsRouting(definition) ||
|
|
52
|
+
values.requesterType !== 'studentRecords'
|
|
53
|
+
) {
|
|
54
|
+
return values
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const options = definition.fields.find(
|
|
58
|
+
field => field.name === 'transferRecordTypes'
|
|
59
|
+
).options
|
|
60
|
+
const labels = values.transferRecordTypes.map(value => {
|
|
61
|
+
const label = options.find(option => option.value === value).label
|
|
62
|
+
return value === 'otherRecords'
|
|
63
|
+
? `${label}: ${values.otherRecordsDescription}`
|
|
64
|
+
: label
|
|
65
|
+
})
|
|
66
|
+
return {
|
|
67
|
+
...values,
|
|
68
|
+
recordsRequested: labels.join('\n'),
|
|
69
|
+
transferRecordTypes: [],
|
|
70
|
+
otherRecordsDescription: '',
|
|
71
|
+
}
|
|
72
|
+
}
|