@asd20/ui-next 2.30.0 → 2.31.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 +21 -0
- package/README.md +4 -0
- package/components.js +2 -0
- package/helpers.js +8 -0
- package/package.json +2 -1
- package/src/components/atoms/Asd20Messaging/index.vue +6 -1
- package/src/components/atoms/Asd20RichBodyContent/headingContext.js +255 -0
- package/src/components/atoms/Asd20RichBodyContent/index.vue +31 -0
- package/src/components/atoms/Asd20RichBodyContent/messageDocumentRenderer.js +199 -45
- package/src/components/molecules/Asd20Accordion/index.vue +38 -14
- package/src/components/molecules/Asd20BodyAccordion/index.vue +23 -2
- package/src/components/molecules/Asd20PrintContents/index.vue +42 -0
- package/src/components/molecules/Asd20PrintHeader/index.vue +54 -0
- package/src/components/molecules/Asd20PublicFormField/index.vue +149 -33
- package/src/components/molecules/Asd20TableauEmbed/index.vue +1 -0
- package/src/components/organisms/Asd20PublicFormModal/index.vue +9 -0
- package/src/components/templates/Asd20HandbookDocument/index.vue +85 -0
- package/src/components/templates/Asd20HandbookTemplate/index.vue +121 -69
- package/src/design/_document-print.scss +104 -0
- package/src/design/_print.scss +2 -0
- package/src/helpers/documentPrint.js +184 -0
- package/src/helpers/handbookDocument.js +44 -0
- package/src/helpers/publicFormDefinition.js +13 -6
- package/src/helpers/publicationIntroduction.js +53 -0
- package/templates.js +1 -0
|
@@ -107,6 +107,7 @@ function normalizeField(field) {
|
|
|
107
107
|
),
|
|
108
108
|
options: undefined,
|
|
109
109
|
visibleWhen: normalizeCondition(field.visibleWhen),
|
|
110
|
+
requiredUnlessUploaded: boundedText(field.requiredUnlessUploaded, 'Upload field name', 64),
|
|
110
111
|
requiredWhen:
|
|
111
112
|
field.requiredWhen == null
|
|
112
113
|
? undefined
|
|
@@ -191,13 +192,13 @@ export function isPublicFormFieldVisible(field, values) {
|
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
export function isPublicFormFieldRequired(field, values) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
field.requiredWhen?.some(condition =>
|
|
197
|
-
condition.values.includes(String(values[condition.fieldName] || '').trim())
|
|
198
|
-
) ||
|
|
199
|
-
false
|
|
195
|
+
const required = field.required || field.requiredWhen?.some(condition =>
|
|
196
|
+
condition.values.includes(String(values[condition.fieldName] || '').trim())
|
|
200
197
|
)
|
|
198
|
+
const uploaded = field.requiredUnlessUploaded &&
|
|
199
|
+
Array.isArray(values[field.requiredUnlessUploaded]) &&
|
|
200
|
+
values[field.requiredUnlessUploaded].length > 0
|
|
201
|
+
return Boolean(required && !uploaded)
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
export async function serializePublicFormValues(definition, values) {
|
|
@@ -252,6 +253,12 @@ export function normalizePublicFormDefinition(definition) {
|
|
|
252
253
|
throw new Error('The public form contains duplicate field names.')
|
|
253
254
|
|
|
254
255
|
for (const field of fields) {
|
|
256
|
+
if (field.requiredUnlessUploaded) {
|
|
257
|
+
const upload = fields.find(candidate => candidate.name === field.requiredUnlessUploaded)
|
|
258
|
+
if (!upload || upload.type !== 'file' || upload === field) {
|
|
259
|
+
throw new Error('The public form has an invalid upload requirement reference.')
|
|
260
|
+
}
|
|
261
|
+
}
|
|
255
262
|
for (const condition of [
|
|
256
263
|
field.visibleWhen,
|
|
257
264
|
...(field.requiredWhen || []),
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { parseFragment } from 'parse5'
|
|
2
|
+
|
|
3
|
+
// Only empty paragraphs/whitespace are editorially empty. Media, placeholders,
|
|
4
|
+
// links, tables, and otherwise unknown elements must remain visible to review.
|
|
5
|
+
function nonemptyHtml(html) {
|
|
6
|
+
const visit = node => {
|
|
7
|
+
if (node.nodeName === '#text') return Boolean(node.value.trim())
|
|
8
|
+
if (node.tagName && node.tagName !== 'p') return true
|
|
9
|
+
return (node.childNodes || []).some(visit)
|
|
10
|
+
}
|
|
11
|
+
return (parseFragment(String(html || '')).childNodes || []).some(visit)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function nonemptyJson(value) {
|
|
15
|
+
if (!value) return false
|
|
16
|
+
let doc = value
|
|
17
|
+
if (typeof value === 'string') {
|
|
18
|
+
try { doc = JSON.parse(value) } catch { return true }
|
|
19
|
+
}
|
|
20
|
+
if (doc?.version === 1 && Array.isArray(doc.blocks)) return doc.blocks.length > 0
|
|
21
|
+
const visit = node => {
|
|
22
|
+
if (node?.type === 'text') return Boolean(node.text?.trim())
|
|
23
|
+
if (!['doc', 'paragraph'].includes(node?.type)) return true
|
|
24
|
+
return (node.content || []).some(visit)
|
|
25
|
+
}
|
|
26
|
+
return visit(doc)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function buildPublicationIntroduction(firstMessage, metaDescription = '') {
|
|
30
|
+
const message = firstMessage || {}
|
|
31
|
+
const lead = message.shortDescription || message.summary || metaDescription || ''
|
|
32
|
+
const excludedFields = ['heading', 'title', 'bodyContent', 'bodyJson', 'longDescription', 'images', 'callsToAction', 'detailLink', 'detailLinkLabel']
|
|
33
|
+
const unmappedFields = []
|
|
34
|
+
for (const field of ['bodyContent', 'longDescription']) {
|
|
35
|
+
if (nonemptyHtml(message[field])) unmappedFields.push(field)
|
|
36
|
+
}
|
|
37
|
+
if (nonemptyJson(message.bodyJson)) unmappedFields.push('bodyJson')
|
|
38
|
+
for (const field of ['images', 'callsToAction']) {
|
|
39
|
+
if (message[field]?.length) unmappedFields.push(field)
|
|
40
|
+
}
|
|
41
|
+
// Detail links direct readers from wayfinding messages to detailed pages.
|
|
42
|
+
// The handbook already is that destination; their omission is intentional.
|
|
43
|
+
// Only explicitly typed detail links qualify. Other links still need review.
|
|
44
|
+
if (message.links?.some(item => (item?.link ?? item)?.type !== 'Detail Link')) unmappedFields.push('links')
|
|
45
|
+
if (message.shortDescription && nonemptyHtml(message.summary) && message.summary !== lead) unmappedFields.push('summary')
|
|
46
|
+
return {
|
|
47
|
+
messageId: message.id ?? null, mode: 'first-summary-lead', lead,
|
|
48
|
+
detailLinkPolicy: 'omit-wayfinding',
|
|
49
|
+
leadSource: message.shortDescription || message.summary ? 'summary' : metaDescription ? 'metaDescription' : 'none',
|
|
50
|
+
excludedFields: [...excludedFields, 'links', ...(message.shortDescription ? ['summary'] : [])],
|
|
51
|
+
diagnostics: unmappedFields.length ? [{ code: 'introduction-content-unmapped', fields: unmappedFields }] : [],
|
|
52
|
+
}
|
|
53
|
+
}
|
package/templates.js
CHANGED
|
@@ -27,3 +27,4 @@ export { default as Asd20SearchAppTemplate } from './src/components/templates/As
|
|
|
27
27
|
export { default as Asd20WayfindingAlternateTemplate } from './src/components/templates/Asd20WayfindingAlternateTemplate/index.vue'
|
|
28
28
|
export { default as Asd20WayfindingImageTemplate } from './src/components/templates/Asd20WayfindingImageTemplate/index.vue'
|
|
29
29
|
export { default as Asd20WayfindingTemplate } from './src/components/templates/Asd20WayfindingTemplate/index.vue'
|
|
30
|
+
export { default as Asd20HandbookDocument } from './src/components/templates/Asd20HandbookDocument/index.vue'
|