@asd20/ui-next 2.33.3 → 2.34.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.34.1](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.34.0...ui-next-v2.34.1) (2026-10-28)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * edit order of records on student records request form ([6880af6](https://github.com/academydistrict20/asd20-ui-next/commit/6880af607ab042d62f59b4b10da770f99e573cba))
9
+
10
+ # [2.34.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.33.3...ui-next-v2.34.0) (2026-10-28)
11
+
12
+
13
+ ### Features
14
+
15
+ * support checkbox groups for school transfer records ([9596d21](https://github.com/academydistrict20/asd20-ui-next/commit/9596d21fea018afec4c03de64471549cb77e9487))
16
+
3
17
  ## [2.33.3](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.33.2...ui-next-v2.33.3) (2026-10-27)
4
18
 
5
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.33.3",
3
+ "version": "2.34.1",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -149,6 +149,33 @@
149
149
  </label>
150
150
  </fieldset>
151
151
 
152
+ <fieldset
153
+ v-else-if="field.type === 'checkbox-group'"
154
+ :id="inputId"
155
+ class="asd20-public-form-field__choices"
156
+ tabindex="-1"
157
+ :aria-describedby="describedBy"
158
+ :aria-invalid="hasErrors ? 'true' : undefined"
159
+ >
160
+ <legend>{{ displayLabel }}</legend>
161
+ <label
162
+ v-for="option in field.options"
163
+ :key="option.value"
164
+ :class="{ 'asd20-public-form-field__option--separated': option.separatorAfter }"
165
+ :for="`${inputId}-${option.value}`"
166
+ >
167
+ <input
168
+ :id="`${inputId}-${option.value}`"
169
+ :name="inputId"
170
+ type="checkbox"
171
+ :value="option.value"
172
+ :checked="Array.isArray(modelValue) && modelValue.includes(option.value)"
173
+ @change="toggleChoice(option.value, $event.target.checked)"
174
+ />
175
+ <span>{{ option.label }}</span>
176
+ </label>
177
+ </fieldset>
178
+
152
179
  <asd20-checkbox
153
180
  v-else-if="field.type === 'checkbox'"
154
181
  :id="inputId"
@@ -276,6 +303,12 @@ export default {
276
303
  },
277
304
  },
278
305
  methods: {
306
+ toggleChoice(value, checked) {
307
+ const selected = Array.isArray(this.modelValue) ? this.modelValue : []
308
+ this.$emit('update:modelValue', checked
309
+ ? [...selected.filter(choice => choice !== value), value]
310
+ : selected.filter(choice => choice !== value))
311
+ },
279
312
  addFiles(event) {
280
313
  const addedFiles = Array.from(event.target.files || [])
281
314
  if (addedFiles.length) {
@@ -407,7 +440,14 @@ export default {
407
440
  align-items: flex-start;
408
441
  }
409
442
 
410
- &__choices input[type='radio'] {
443
+ &__choices label.asd20-public-form-field__option--separated {
444
+ border-bottom: 1px solid #d9d9d9;
445
+ margin-bottom: space(0.25);
446
+ padding-bottom: space(0.25);
447
+ }
448
+
449
+ &__choices input[type='radio'],
450
+ &__choices input[type='checkbox'] {
411
451
  flex: 0 0 auto;
412
452
  margin: 0;
413
453
  }
@@ -410,7 +410,7 @@ export default {
410
410
  for (const field of this.formDefinition.fields) {
411
411
  if (!isPublicFormFieldVisible(field, this.values)) {
412
412
  this.values[field.name] =
413
- field.type === 'checkbox' ? false : field.type === 'file' ? [] : ''
413
+ field.type === 'checkbox' ? false : ['file', 'checkbox-group'].includes(field.type) ? [] : ''
414
414
  }
415
415
  }
416
416
  if (this.validationAttempted) this.validate(false)
@@ -7,6 +7,7 @@ export const SUPPORTED_PUBLIC_FORM_FIELD_TYPES = Object.freeze([
7
7
  'select',
8
8
  'radio',
9
9
  'checkbox',
10
+ 'checkbox-group',
10
11
  'file',
11
12
  ])
12
13
 
@@ -48,7 +49,7 @@ function optionalBoolean(value, label) {
48
49
  }
49
50
 
50
51
  function normalizeOptions(field) {
51
- if (!['select', 'radio'].includes(field.type)) return undefined
52
+ if (!['select', 'radio', 'checkbox-group'].includes(field.type)) return undefined
52
53
  if (
53
54
  !Array.isArray(field.options) ||
54
55
  !field.options.length ||
@@ -71,7 +72,13 @@ function normalizeOptions(field) {
71
72
  'Option description',
72
73
  1000
73
74
  )
74
- return description ? { label, value, description } : { label, value }
75
+ const separatorAfter = optionalBoolean(option?.separatorAfter, 'Option separator')
76
+ return {
77
+ label,
78
+ value,
79
+ ...(description ? { description } : {}),
80
+ ...(separatorAfter ? { separatorAfter: true } : {}),
81
+ }
75
82
  })
76
83
  }
77
84
 
@@ -184,17 +191,19 @@ function normalizeConditions(conditions) {
184
191
  return conditions.map(normalizeCondition)
185
192
  }
186
193
 
194
+ function matchesCondition(condition, values) {
195
+ const value = values[condition.fieldName]
196
+ return Array.isArray(value)
197
+ ? value.some(choice => condition.values.includes(choice))
198
+ : condition.values.includes(String(value || '').trim())
199
+ }
200
+
187
201
  export function isPublicFormFieldVisible(field, values) {
188
- return (
189
- !field.visibleWhen ||
190
- field.visibleWhen.values.includes(String(values[field.visibleWhen.fieldName] || '').trim())
191
- )
202
+ return !field.visibleWhen || matchesCondition(field.visibleWhen, values)
192
203
  }
193
204
 
194
205
  export function isPublicFormFieldRequired(field, values) {
195
- const required = field.required || field.requiredWhen?.some(condition =>
196
- condition.values.includes(String(values[condition.fieldName] || '').trim())
197
- )
206
+ const required = field.required || field.requiredWhen?.some(condition => matchesCondition(condition, values))
198
207
  const uploaded = field.requiredUnlessUploaded &&
199
208
  Array.isArray(values[field.requiredUnlessUploaded]) &&
200
209
  values[field.requiredUnlessUploaded].length > 0
@@ -268,8 +277,8 @@ export function normalizePublicFormDefinition(definition) {
268
277
  )
269
278
  if (
270
279
  !dependency ||
271
- dependency === field ||
272
- !['radio', 'select'].includes(dependency.type) ||
280
+ fields.indexOf(dependency) >= fields.indexOf(field) ||
281
+ !['radio', 'select', 'checkbox-group'].includes(dependency.type) ||
273
282
  condition.values.some(
274
283
  value => !dependency.options.some(option => option.value === value)
275
284
  )
@@ -369,7 +378,7 @@ export function createPublicFormValues(definition) {
369
378
  return Object.fromEntries(
370
379
  definition.fields.map(field => [
371
380
  field.name,
372
- field.type === 'file' ? [] : field.type === 'checkbox' ? false : '',
381
+ ['file', 'checkbox-group'].includes(field.type) ? [] : field.type === 'checkbox' ? false : '',
373
382
  ])
374
383
  )
375
384
  }
@@ -403,9 +412,9 @@ export function validatePublicFormValues(definition, sourceValues = {}) {
403
412
  const values = {}
404
413
 
405
414
  for (const field of definition.fields) {
406
- if (!isPublicFormFieldVisible(field, sourceValues)) {
415
+ if (!isPublicFormFieldVisible(field, values)) {
407
416
  values[field.name] =
408
- field.type === 'file' ? [] : field.type === 'checkbox' ? false : ''
417
+ ['file', 'checkbox-group'].includes(field.type) ? [] : field.type === 'checkbox' ? false : ''
409
418
  continue
410
419
  }
411
420
  const required = isPublicFormFieldRequired(field, sourceValues)
@@ -433,6 +442,21 @@ export function validatePublicFormValues(definition, sourceValues = {}) {
433
442
  'Upload up to 5 PDF documents, 5 MB each and 10 MB total.'
434
443
  )
435
444
  }
445
+ } else if (field.type === 'checkbox-group') {
446
+ const selected = rawValue == null ? [] : rawValue
447
+ const allowed = new Set((field.options || []).map(option => option.value))
448
+ if (
449
+ !Array.isArray(selected) ||
450
+ selected.length > allowed.size ||
451
+ selected.some(choice => typeof choice !== 'string' || !allowed.has(choice)) ||
452
+ new Set(selected).size !== selected.length
453
+ ) {
454
+ values[field.name] = []
455
+ fieldErrors.push(`Select valid choices for ${field.label.toLowerCase()}.`)
456
+ } else {
457
+ values[field.name] = [...selected]
458
+ if (required && !selected.length) fieldErrors.push(`${field.label} is required.`)
459
+ }
436
460
  } else if (field.type === 'checkbox') {
437
461
  const value = rawValue === true
438
462
  values[field.name] = value