@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
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildHeadingIdPlan,
|
|
3
|
+
createHeadingSession,
|
|
4
|
+
contextualAccordionItemKey,
|
|
5
|
+
normalizeHeadingContext,
|
|
6
|
+
} from './headingContext'
|
|
7
|
+
|
|
1
8
|
const MESSAGE_DOCUMENT_SCHEMA_VERSION = 1
|
|
2
9
|
|
|
3
10
|
export const BODY_JSON_FORMAT = Object.freeze({
|
|
@@ -690,7 +697,7 @@ function attribute(name, value, preserveEmpty = false) {
|
|
|
690
697
|
: ` ${name}="${escapeHtml(value)}"`
|
|
691
698
|
}
|
|
692
699
|
|
|
693
|
-
function renderMarkedText(node) {
|
|
700
|
+
function renderMarkedText(node, context, path) {
|
|
694
701
|
let html = escapeHtml(node.text)
|
|
695
702
|
const marks = [...(node.marks || [])].sort(
|
|
696
703
|
(left, right) =>
|
|
@@ -705,7 +712,7 @@ function renderMarkedText(node) {
|
|
|
705
712
|
else if (mark.type === 'subscript') html = `<sub>${html}</sub>`
|
|
706
713
|
else if (mark.type === 'superscript') html = `<sup>${html}</sup>`
|
|
707
714
|
else if (mark.type === 'link') {
|
|
708
|
-
html = `<a${attribute('href', mark.attrs.href)}${attribute(
|
|
715
|
+
html = `<a${attribute('href', context ? context.href(mark.attrs.href, `${path}-link`) : mark.attrs.href)}${attribute(
|
|
709
716
|
'target',
|
|
710
717
|
mark.attrs.target
|
|
711
718
|
)}${attribute('rel', mark.attrs.rel)}${attribute(
|
|
@@ -717,11 +724,19 @@ function renderMarkedText(node) {
|
|
|
717
724
|
return html
|
|
718
725
|
}
|
|
719
726
|
|
|
720
|
-
function renderChildren(node) {
|
|
721
|
-
return (node.content || []).map(
|
|
727
|
+
function renderChildren(node, context, path, accordionItemId) {
|
|
728
|
+
return (node.content || []).map((child, index) =>
|
|
729
|
+
renderStandardNode(child, context, `${path}-${index}`, accordionItemId)
|
|
730
|
+
).join('')
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function canonicalHeadingLabel(node) {
|
|
734
|
+
if (node.type === 'text') return node.text
|
|
735
|
+
if (node.type === 'hardBreak') return ' '
|
|
736
|
+
return (node.content || []).map(canonicalHeadingLabel).join('')
|
|
722
737
|
}
|
|
723
738
|
|
|
724
|
-
function renderTableCell(node, tagName) {
|
|
739
|
+
function renderTableCell(node, tagName, context, path, accordionItemId) {
|
|
725
740
|
const colspan = node.attrs?.colspan ?? 1
|
|
726
741
|
const rowspan = node.attrs?.rowspan ?? 1
|
|
727
742
|
const colwidth = node.attrs?.colwidth
|
|
@@ -734,10 +749,10 @@ function renderTableCell(node, tagName) {
|
|
|
734
749
|
Array.isArray(colwidth) ? attribute('colwidth', colwidth.join(',')) : ''
|
|
735
750
|
}${align ? attribute('style', `text-align: ${align}`) : ''}${
|
|
736
751
|
scope ? attribute('scope', scope) : ''
|
|
737
|
-
}>${renderChildren(node)}</${tagName}>`
|
|
752
|
+
}>${renderChildren(node, context, path, accordionItemId)}</${tagName}>`
|
|
738
753
|
}
|
|
739
754
|
|
|
740
|
-
function renderTable(node) {
|
|
755
|
+
function renderTable(node, context, path, accordionItemId) {
|
|
741
756
|
const minimumWidth = 25
|
|
742
757
|
const firstRow = node.content[0]
|
|
743
758
|
const columns = firstRow.content.flatMap(cell => {
|
|
@@ -767,7 +782,7 @@ function renderTable(node) {
|
|
|
767
782
|
'style',
|
|
768
783
|
tableStyle
|
|
769
784
|
)}>${caption}<colgroup>${colgroup}</colgroup><tbody>${renderChildren(
|
|
770
|
-
node
|
|
785
|
+
node, context, path, accordionItemId
|
|
771
786
|
)}</tbody></table></div>`
|
|
772
787
|
}
|
|
773
788
|
|
|
@@ -782,34 +797,41 @@ function renderImage(attrs, className = '') {
|
|
|
782
797
|
)}${attribute('height', attrs.height)}>`
|
|
783
798
|
}
|
|
784
799
|
|
|
785
|
-
function renderStandardNode(node) {
|
|
786
|
-
if (node.type === 'text') return renderMarkedText(node)
|
|
800
|
+
function renderStandardNode(node, context, path, accordionItemId) {
|
|
801
|
+
if (node.type === 'text') return renderMarkedText(node, context, path)
|
|
787
802
|
if (node.type === 'hardBreak') return '<br>'
|
|
788
|
-
if (node.type === 'paragraph') return `<p>${renderChildren(node)}</p>`
|
|
803
|
+
if (node.type === 'paragraph') return `<p>${renderChildren(node, context, path, accordionItemId)}</p>`
|
|
789
804
|
if (node.type === 'heading') {
|
|
790
|
-
|
|
805
|
+
if (context) {
|
|
806
|
+
const descriptor = context.heading({
|
|
807
|
+
authoredLevel: node.attrs.level, label: canonicalHeadingLabel(node),
|
|
808
|
+
sourcePath: path, accordionItemId,
|
|
809
|
+
})
|
|
810
|
+
return `<h${descriptor.renderedLevel}${attribute('id', descriptor.id)}>${renderChildren(node, context, path, accordionItemId)}</h${descriptor.renderedLevel}>`
|
|
811
|
+
}
|
|
812
|
+
return `<h${node.attrs.level}>${renderChildren(node, context, path, accordionItemId)}</h${
|
|
791
813
|
node.attrs.level
|
|
792
814
|
}>`
|
|
793
815
|
}
|
|
794
816
|
if (node.type === 'blockquote') {
|
|
795
|
-
return `<blockquote>${renderChildren(node)}</blockquote>`
|
|
817
|
+
return `<blockquote>${renderChildren(node, context, path, accordionItemId)}</blockquote>`
|
|
796
818
|
}
|
|
797
819
|
if (node.type === 'codeBlock') {
|
|
798
820
|
const languageClass = node.attrs?.language
|
|
799
821
|
? attribute('class', `language-${node.attrs.language}`)
|
|
800
822
|
: ''
|
|
801
|
-
return `<pre><code${languageClass}>${renderChildren(node)}</code></pre>`
|
|
823
|
+
return `<pre><code${languageClass}>${renderChildren(node, context, path, accordionItemId)}</code></pre>`
|
|
802
824
|
}
|
|
803
825
|
if (node.type === 'horizontalRule') return '<hr>'
|
|
804
|
-
if (node.type === 'bulletList') return `<ul>${renderChildren(node)}</ul>`
|
|
826
|
+
if (node.type === 'bulletList') return `<ul>${renderChildren(node, context, path, accordionItemId)}</ul>`
|
|
805
827
|
if (node.type === 'orderedList') {
|
|
806
828
|
const start = node.attrs?.start
|
|
807
829
|
const type = node.attrs?.type
|
|
808
830
|
return `<ol${start && start !== 1 ? attribute('start', start) : ''}${
|
|
809
831
|
type && type !== '1' ? attribute('type', type) : ''
|
|
810
|
-
}>${renderChildren(node)}</ol>`
|
|
832
|
+
}>${renderChildren(node, context, path, accordionItemId)}</ol>`
|
|
811
833
|
}
|
|
812
|
-
if (node.type === 'listItem') return `<li>${renderChildren(node)}</li>`
|
|
834
|
+
if (node.type === 'listItem') return `<li>${renderChildren(node, context, path, accordionItemId)}</li>`
|
|
813
835
|
if (node.type === 'image') {
|
|
814
836
|
return renderImage(node.attrs, 'asd20-message-image')
|
|
815
837
|
}
|
|
@@ -844,10 +866,10 @@ function renderStandardNode(node) {
|
|
|
844
866
|
: ''
|
|
845
867
|
}</div>`
|
|
846
868
|
}
|
|
847
|
-
if (node.type === 'table') return renderTable(node)
|
|
848
|
-
if (node.type === 'tableRow') return `<tr>${renderChildren(node)}</tr>`
|
|
849
|
-
if (node.type === 'tableCell') return renderTableCell(node, 'td')
|
|
850
|
-
if (node.type === 'tableHeader') return renderTableCell(node, 'th')
|
|
869
|
+
if (node.type === 'table') return renderTable(node, context, path, accordionItemId)
|
|
870
|
+
if (node.type === 'tableRow') return `<tr>${renderChildren(node, context, path, accordionItemId)}</tr>`
|
|
871
|
+
if (node.type === 'tableCell') return renderTableCell(node, 'td', context, path, accordionItemId)
|
|
872
|
+
if (node.type === 'tableHeader') return renderTableCell(node, 'th', context, path, accordionItemId)
|
|
851
873
|
return ''
|
|
852
874
|
}
|
|
853
875
|
|
|
@@ -878,47 +900,87 @@ function buildLegacyRenderNodes(bodyHtml, blocks) {
|
|
|
878
900
|
return nodes
|
|
879
901
|
}
|
|
880
902
|
|
|
881
|
-
function accordionToRenderBlock(node) {
|
|
903
|
+
function accordionToRenderBlock(node, context, path) {
|
|
882
904
|
return {
|
|
883
905
|
id: node.attrs?.blockId || '',
|
|
884
906
|
type: 'accordion',
|
|
885
|
-
items: node.content.map(item => {
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
? {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
907
|
+
items: node.content.map((item, index) => {
|
|
908
|
+
const renderItem = () => {
|
|
909
|
+
const itemPath = `${path}-${index}`
|
|
910
|
+
const contextual = context ? contextualAccordionHeading(context, item.attrs?.heading || '', itemPath) : {}
|
|
911
|
+
const isTableau = item.content[0]?.type === 'tableauBlock'
|
|
912
|
+
return isTableau
|
|
913
|
+
? {
|
|
914
|
+
id: item.attrs?.itemId || '',
|
|
915
|
+
heading: item.attrs?.heading || '',
|
|
916
|
+
...contextual,
|
|
917
|
+
opened: Boolean(item.attrs?.opened),
|
|
918
|
+
contentType: 'tableau',
|
|
919
|
+
tableau: { ...item.content[0].attrs },
|
|
920
|
+
}
|
|
921
|
+
: {
|
|
922
|
+
id: item.attrs?.itemId || '',
|
|
923
|
+
heading: item.attrs?.heading || '',
|
|
924
|
+
...contextual,
|
|
925
|
+
opened: Boolean(item.attrs?.opened),
|
|
926
|
+
contentType: 'text',
|
|
927
|
+
bodyHtml: item.content.map((child, childIndex) => renderStandardNode(
|
|
928
|
+
child, context, `${itemPath}-${childIndex}`, context ? itemPath : null
|
|
929
|
+
)).join(''),
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return context ? context.scope(renderItem) : renderItem()
|
|
902
933
|
}),
|
|
903
934
|
}
|
|
904
935
|
}
|
|
905
936
|
|
|
906
|
-
function
|
|
937
|
+
function contextualAccordionHeading(context, label, path) {
|
|
938
|
+
const descriptor = context.heading({
|
|
939
|
+
authoredLevel: 3, label, sourcePath: `${path}-heading`,
|
|
940
|
+
kind: 'accordion-item', accordionItemId: path,
|
|
941
|
+
})
|
|
942
|
+
const panel = context.anchor(`${path}-panel`, null,
|
|
943
|
+
`${context.context.idPrefix || 'body'}-${path}-panel`, 'panel')
|
|
944
|
+
return {
|
|
945
|
+
[contextualAccordionItemKey]: true,
|
|
946
|
+
headingContextApplied: true,
|
|
947
|
+
headingTag: `h${descriptor.renderedLevel}`,
|
|
948
|
+
headingId: descriptor.id,
|
|
949
|
+
contentId: panel.id,
|
|
950
|
+
headingDescriptor: descriptor,
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function contextualLegacyAccordion(block, context, path) {
|
|
955
|
+
return {
|
|
956
|
+
...block,
|
|
957
|
+
items: (Array.isArray(block.items) ? block.items : []).map((item, index) => context.scope(() => {
|
|
958
|
+
if (item?.[contextualAccordionItemKey]) return item
|
|
959
|
+
const itemPath = `${path}-item-${index}`
|
|
960
|
+
const contextual = contextualAccordionHeading(context, item?.heading || '', itemPath)
|
|
961
|
+
const body = context.html(item?.bodyHtml || '', `${itemPath}-body`, null, itemPath)
|
|
962
|
+
return { ...item, ...contextual, bodyHtml: body.html }
|
|
963
|
+
})),
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function buildTiptapRenderNodes(messageDocument, context) {
|
|
907
968
|
const nodes = []
|
|
908
969
|
let standardContent = []
|
|
909
970
|
const flushStandardContent = () => {
|
|
910
971
|
if (!standardContent.length) return
|
|
911
972
|
nodes.push({
|
|
912
973
|
type: 'html',
|
|
913
|
-
html: standardContent.map(renderStandardNode).join(''),
|
|
974
|
+
html: standardContent.map(({ node, path }) => renderStandardNode(node, context, path)).join(''),
|
|
914
975
|
})
|
|
915
976
|
standardContent = []
|
|
916
977
|
}
|
|
917
978
|
|
|
918
|
-
messageDocument.content.forEach(node => {
|
|
979
|
+
messageDocument.content.forEach((node, index) => {
|
|
980
|
+
const path = `node-${index}`
|
|
919
981
|
if (node.type === 'accordionBlock') {
|
|
920
982
|
flushStandardContent()
|
|
921
|
-
nodes.push({ type: 'accordion', block: accordionToRenderBlock(node) })
|
|
983
|
+
nodes.push({ type: 'accordion', block: accordionToRenderBlock(node, context, path) })
|
|
922
984
|
} else if (node.type === 'tableauBlock') {
|
|
923
985
|
flushStandardContent()
|
|
924
986
|
nodes.push({
|
|
@@ -930,7 +992,7 @@ function buildTiptapRenderNodes(messageDocument) {
|
|
|
930
992
|
},
|
|
931
993
|
})
|
|
932
994
|
} else {
|
|
933
|
-
standardContent.push(node)
|
|
995
|
+
standardContent.push({ node, path })
|
|
934
996
|
}
|
|
935
997
|
})
|
|
936
998
|
flushStandardContent()
|
|
@@ -954,7 +1016,7 @@ export function containsBodyBlockPlaceholder(html) {
|
|
|
954
1016
|
return new RegExp(LEGACY_PLACEHOLDER_PATTERN).test(html)
|
|
955
1017
|
}
|
|
956
1018
|
|
|
957
|
-
|
|
1019
|
+
function buildBaseRichBodyRenderModel({
|
|
958
1020
|
bodyContent = '',
|
|
959
1021
|
bodyJson = '',
|
|
960
1022
|
} = {}) {
|
|
@@ -996,3 +1058,95 @@ export function buildRichBodyRenderModel({
|
|
|
996
1058
|
nodes: buildTiptapRenderNodes(detected.value),
|
|
997
1059
|
}
|
|
998
1060
|
}
|
|
1061
|
+
|
|
1062
|
+
function renderContextualBody(input, baseModel, session) {
|
|
1063
|
+
if (baseModel.status === MESSAGE_DOCUMENT_STATUS.SUPPORTED) {
|
|
1064
|
+
const { parsed } = parseBodyJson(input.bodyJson)
|
|
1065
|
+
const nodes = buildTiptapRenderNodes(parsed, session)
|
|
1066
|
+
return { ...baseModel, nodes, initialNodes: nodes }
|
|
1067
|
+
}
|
|
1068
|
+
const blocks = baseModel.status === MESSAGE_DOCUMENT_STATUS.LEGACY ? baseModel.blocks : []
|
|
1069
|
+
const blockMap = new Map(blocks.map(block => [block.id, block]))
|
|
1070
|
+
const transformed = session.html(input.bodyContent || '', 'body', (type, id, path) => {
|
|
1071
|
+
const block = blockMap.get(id)
|
|
1072
|
+
if (!block || block.type !== type) return null
|
|
1073
|
+
if (type === 'accordion') {
|
|
1074
|
+
return { type, block: contextualLegacyAccordion(block, session, `html-body-block-${path}`) }
|
|
1075
|
+
}
|
|
1076
|
+
return type === 'tableau' ? { type, block: { ...block } } : null
|
|
1077
|
+
})
|
|
1078
|
+
return {
|
|
1079
|
+
...baseModel,
|
|
1080
|
+
nodes: transformed.nodes,
|
|
1081
|
+
initialNodes: transformed.html ? [{ type: 'html', html: transformed.html }] : [],
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function finalizeContextualModel(render, context, suppliedPlan = null) {
|
|
1086
|
+
let plan = suppliedPlan || context.idPlan
|
|
1087
|
+
if (!plan) {
|
|
1088
|
+
const draft = createHeadingSession(context)
|
|
1089
|
+
render(draft)
|
|
1090
|
+
plan = buildHeadingIdPlan([draft], context.reservedIds || [])
|
|
1091
|
+
}
|
|
1092
|
+
const session = createHeadingSession({ ...context, idPlan: plan })
|
|
1093
|
+
const model = render(session)
|
|
1094
|
+
const prefix = context.idPrefix || 'body'
|
|
1095
|
+
if (!suppliedPlan && !context.idPlan) session.report('document-heading-plan-unavailable', prefix)
|
|
1096
|
+
const planDiagnostics = plan.diagnostics.filter(row => row.sourceKey.startsWith(`${prefix}:`))
|
|
1097
|
+
for (const descriptor of session.headings) {
|
|
1098
|
+
descriptor.diagnostics.push(...planDiagnostics.filter(row => row.sourceKey === descriptor.key).map(row => row.code))
|
|
1099
|
+
}
|
|
1100
|
+
return {
|
|
1101
|
+
...model, headings: session.headings,
|
|
1102
|
+
headingDiagnostics: [...session.diagnostics, ...planDiagnostics],
|
|
1103
|
+
headingContextApplied: true,
|
|
1104
|
+
documentHeadingPlanAvailable: Boolean(suppliedPlan || context.idPlan),
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
export function buildRichBodyRenderModel(input = {}) {
|
|
1109
|
+
const baseModel = buildBaseRichBodyRenderModel(input)
|
|
1110
|
+
const context = normalizeHeadingContext(input.headingContext)
|
|
1111
|
+
if (!context.active) {
|
|
1112
|
+
return { ...baseModel, headings: [], headingDiagnostics: [], headingContextApplied: false }
|
|
1113
|
+
}
|
|
1114
|
+
return finalizeContextualModel(session => renderContextualBody(input, baseModel, session), context)
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// This prepass is deliberately independent of Vue instances and mounted topics.
|
|
1118
|
+
export function buildPublicationHeadingModels({ bodies = [], reservedIds = [], documentUrl } = {}) {
|
|
1119
|
+
const prefixes = new Set()
|
|
1120
|
+
const prepared = bodies.map(body => {
|
|
1121
|
+
const prefix = body.headingIdPrefix
|
|
1122
|
+
if (typeof prefix !== 'string' || !prefix || prefixes.has(prefix)) {
|
|
1123
|
+
throw new TypeError('Publication bodies require unique, nonempty headingIdPrefix values.')
|
|
1124
|
+
}
|
|
1125
|
+
prefixes.add(prefix)
|
|
1126
|
+
const context = normalizeHeadingContext({
|
|
1127
|
+
levelOffset: body.headingLevelOffset === undefined ? 0 : body.headingLevelOffset,
|
|
1128
|
+
idPrefix: prefix, documentUrl,
|
|
1129
|
+
})
|
|
1130
|
+
const baseModel = buildBaseRichBodyRenderModel(body)
|
|
1131
|
+
const render = session => renderContextualBody(body, baseModel, session)
|
|
1132
|
+
const session = createHeadingSession(context)
|
|
1133
|
+
render(session)
|
|
1134
|
+
return { context, render, session }
|
|
1135
|
+
})
|
|
1136
|
+
const idPlan = buildHeadingIdPlan(prepared.map(row => row.session), reservedIds)
|
|
1137
|
+
const models = prepared.map(row => finalizeContextualModel(row.render, row.context, idPlan))
|
|
1138
|
+
return {
|
|
1139
|
+
idPlan, models,
|
|
1140
|
+
headings: models.flatMap(model => model.headings),
|
|
1141
|
+
headingDiagnostics: models.flatMap(model => model.headingDiagnostics),
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
export function buildBodyAccordionRenderModel({ items = [], headingContext = {} } = {}) {
|
|
1146
|
+
const context = normalizeHeadingContext(headingContext)
|
|
1147
|
+
if (!context.active) return { items, headingContextApplied: false }
|
|
1148
|
+
if (items.every(item => item?.[contextualAccordionItemKey])) return { items, headingContextApplied: true }
|
|
1149
|
+
return finalizeContextualModel(session => ({
|
|
1150
|
+
items: contextualLegacyAccordion({ items }, session, 'accordion').items,
|
|
1151
|
+
}), context)
|
|
1152
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
>
|
|
10
10
|
<component
|
|
11
11
|
:is="tag"
|
|
12
|
-
:id="
|
|
12
|
+
:id="resolvedHeadingId"
|
|
13
13
|
class="asd20-accordion__heading"
|
|
14
14
|
>
|
|
15
15
|
<span
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
static
|
|
33
33
|
/>
|
|
34
34
|
<asd20-button
|
|
35
|
-
:aria-labelledby="
|
|
36
|
-
:aria-controls="
|
|
37
|
-
:aria-expanded="
|
|
38
|
-
:icon-angle="
|
|
35
|
+
:aria-labelledby="resolvedHeadingId"
|
|
36
|
+
:aria-controls="resolvedContentId"
|
|
37
|
+
:aria-expanded="isOpen"
|
|
38
|
+
:icon-angle="isOpen ? 180 : 90"
|
|
39
39
|
icon="chevron"
|
|
40
40
|
size="sm"
|
|
41
41
|
transparent
|
|
@@ -44,12 +44,12 @@
|
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
46
|
<div
|
|
47
|
-
:id="
|
|
47
|
+
:id="resolvedContentId"
|
|
48
48
|
ref="content"
|
|
49
|
-
:aria-hidden="
|
|
50
|
-
:inert="
|
|
49
|
+
:aria-hidden="isOpen ? 'false' : 'true'"
|
|
50
|
+
:inert="isOpen ? null : ''"
|
|
51
51
|
:class="{
|
|
52
|
-
'asd20-accordion__content--open':
|
|
52
|
+
'asd20-accordion__content--open': isOpen,
|
|
53
53
|
}"
|
|
54
54
|
class="asd20-accordion__content"
|
|
55
55
|
>
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
import Asd20Badge from '../../atoms/Asd20Badge'
|
|
71
71
|
import Asd20Button from '../../atoms/Asd20Button'
|
|
72
72
|
import Asd20Viewport from '../../atoms/Asd20Viewport'
|
|
73
|
+
import { registerDocumentPrintBlock } from '../../../helpers/documentPrint'
|
|
73
74
|
import createComponentInstanceId from '../../../utils/createComponentInstanceId'
|
|
74
75
|
|
|
75
76
|
const FOCUSABLE_SELECTOR = [
|
|
@@ -88,6 +89,8 @@ export default {
|
|
|
88
89
|
tag: { type: String, default: 'h3' },
|
|
89
90
|
label: { type: String, default: '' },
|
|
90
91
|
heading: { type: String, default: '' },
|
|
92
|
+
headingId: { type: String, default: '' },
|
|
93
|
+
contentId: { type: String, default: '' },
|
|
91
94
|
description: { type: String, default: '' },
|
|
92
95
|
badge: { type: Number, default: -1 },
|
|
93
96
|
opened: { type: Boolean, default: false },
|
|
@@ -99,14 +102,16 @@ export default {
|
|
|
99
102
|
},
|
|
100
103
|
data: () => ({
|
|
101
104
|
open: false,
|
|
105
|
+
printExpanded: false,
|
|
102
106
|
instanceId: createComponentInstanceId('accordion'),
|
|
103
107
|
}),
|
|
104
108
|
computed: {
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
isOpen() { return this.open || this.printExpanded },
|
|
110
|
+
resolvedHeadingId() {
|
|
111
|
+
return this.headingId || `${this.instanceId}-heading`
|
|
107
112
|
},
|
|
108
|
-
|
|
109
|
-
return `${this.instanceId}-content`
|
|
113
|
+
resolvedContentId() {
|
|
114
|
+
return this.contentId || `${this.instanceId}-content`
|
|
110
115
|
},
|
|
111
116
|
classes() {
|
|
112
117
|
return {
|
|
@@ -119,15 +124,34 @@ export default {
|
|
|
119
124
|
this.open = this.opened
|
|
120
125
|
},
|
|
121
126
|
mounted() {
|
|
127
|
+
this.unregisterPrintBlock = registerDocumentPrintBlock(this.$el, () => {
|
|
128
|
+
this.printExpanded = true
|
|
129
|
+
this.syncPrintSemantics()
|
|
130
|
+
return () => {
|
|
131
|
+
this.printExpanded = false
|
|
132
|
+
this.syncPrintSemantics()
|
|
133
|
+
}
|
|
134
|
+
})
|
|
122
135
|
this.syncCollapsedContentInteractivity()
|
|
123
136
|
},
|
|
124
137
|
updated() {
|
|
125
138
|
this.syncCollapsedContentInteractivity()
|
|
126
139
|
},
|
|
127
140
|
beforeUnmount() {
|
|
141
|
+
this.unregisterPrintBlock?.()
|
|
128
142
|
this.restoreCollapsedContentInteractivity()
|
|
129
143
|
},
|
|
130
144
|
methods: {
|
|
145
|
+
syncPrintSemantics() {
|
|
146
|
+
// beforeprint is synchronous: update accessibility state before Vue's tick.
|
|
147
|
+
const panel = this.$refs.content
|
|
148
|
+
panel?.setAttribute('aria-hidden', this.isOpen ? 'false' : 'true')
|
|
149
|
+
if (this.isOpen) panel?.removeAttribute('inert')
|
|
150
|
+
else panel?.setAttribute('inert', '')
|
|
151
|
+
panel?.classList.toggle('asd20-accordion__content--open', this.isOpen)
|
|
152
|
+
this.$el.querySelector('[aria-controls]')?.setAttribute('aria-expanded', String(this.isOpen))
|
|
153
|
+
this.syncCollapsedContentInteractivity()
|
|
154
|
+
},
|
|
131
155
|
toggleOpen: function() {
|
|
132
156
|
this.open = !this.open
|
|
133
157
|
if (this.open) this.$emit('open')
|
|
@@ -140,7 +164,7 @@ export default {
|
|
|
140
164
|
const focusableElements = content.querySelectorAll(FOCUSABLE_SELECTOR)
|
|
141
165
|
|
|
142
166
|
focusableElements.forEach(element => {
|
|
143
|
-
if (this.
|
|
167
|
+
if (this.isOpen) {
|
|
144
168
|
if (!Object.prototype.hasOwnProperty.call(element.dataset, 'accordionOriginalTabindex')) {
|
|
145
169
|
return
|
|
146
170
|
}
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
>
|
|
6
6
|
<asd20-accordion
|
|
7
7
|
v-for="item in normalizedItems"
|
|
8
|
-
:key="item.id"
|
|
8
|
+
:key="item.headingId || item.id"
|
|
9
9
|
:heading="item.heading"
|
|
10
10
|
:opened="item.opened"
|
|
11
|
+
:tag="item.headingTag || 'h3'"
|
|
12
|
+
:heading-id="item.headingId || ''"
|
|
13
|
+
:content-id="item.contentId || ''"
|
|
11
14
|
class="asd20-body-accordion__item"
|
|
12
15
|
>
|
|
13
16
|
<div
|
|
@@ -30,13 +33,21 @@
|
|
|
30
33
|
<script>
|
|
31
34
|
import Asd20Accordion from '../Asd20Accordion'
|
|
32
35
|
import Asd20TableauEmbed from '../Asd20TableauEmbed'
|
|
36
|
+
import { buildBodyAccordionRenderModel } from '../../atoms/Asd20RichBodyContent/messageDocumentRenderer'
|
|
37
|
+
import { publicationHeadingPlanKey } from '../../atoms/Asd20RichBodyContent/headingContext'
|
|
33
38
|
|
|
34
39
|
export default {
|
|
35
40
|
name: 'Asd20BodyAccordion',
|
|
36
41
|
|
|
37
42
|
components: { Asd20Accordion, Asd20TableauEmbed },
|
|
38
43
|
|
|
44
|
+
inject: {
|
|
45
|
+
publicationHeadingPlan: { from: publicationHeadingPlanKey, default: null },
|
|
46
|
+
},
|
|
47
|
+
|
|
39
48
|
props: {
|
|
49
|
+
headingLevelOffset: { type: Number, default: 0 },
|
|
50
|
+
headingIdPrefix: { type: String, default: '' },
|
|
40
51
|
items: {
|
|
41
52
|
type: Array,
|
|
42
53
|
default: () => [],
|
|
@@ -45,10 +56,20 @@ export default {
|
|
|
45
56
|
|
|
46
57
|
computed: {
|
|
47
58
|
normalizedItems() {
|
|
48
|
-
|
|
59
|
+
const model = buildBodyAccordionRenderModel({
|
|
60
|
+
items: Array.isArray(this.items) ? this.items : [],
|
|
61
|
+
headingContext: {
|
|
62
|
+
levelOffset: this.headingLevelOffset, idPrefix: this.headingIdPrefix,
|
|
63
|
+
idPlan: this.publicationHeadingPlan?.(),
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
return model.items
|
|
49
67
|
.map((item, index) => ({
|
|
50
68
|
id: item?.id || `accordion-item-${index}`,
|
|
51
69
|
heading: item?.heading || '',
|
|
70
|
+
headingTag: model.headingContextApplied ? item?.headingTag || '' : '',
|
|
71
|
+
headingId: model.headingContextApplied ? item?.headingId || '' : '',
|
|
72
|
+
contentId: model.headingContextApplied ? item?.contentId || '' : '',
|
|
52
73
|
contentType: item?.contentType === 'tableau' ? 'tableau' : 'text',
|
|
53
74
|
bodyHtml: item?.bodyHtml || '',
|
|
54
75
|
opened: Boolean(item?.opened),
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
class="asd20-print-contents asd20-print-only"
|
|
4
|
+
:aria-label="label"
|
|
5
|
+
>
|
|
6
|
+
<p class="asd20-print-contents__label">
|
|
7
|
+
{{ label }}
|
|
8
|
+
</p>
|
|
9
|
+
<ol>
|
|
10
|
+
<li
|
|
11
|
+
v-for="heading in entries"
|
|
12
|
+
:key="heading.id"
|
|
13
|
+
:style="{ marginInlineStart: `${Math.max(0, heading.renderedLevel - 2)}em` }"
|
|
14
|
+
>
|
|
15
|
+
<a :href="`#${heading.id}`">
|
|
16
|
+
<span>{{ heading.label }}</span>
|
|
17
|
+
<span
|
|
18
|
+
class="asd20-print-contents__page"
|
|
19
|
+
:data-asd20-toc-target="heading.id"
|
|
20
|
+
/>
|
|
21
|
+
</a>
|
|
22
|
+
</li>
|
|
23
|
+
</ol>
|
|
24
|
+
</nav>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
export default {
|
|
29
|
+
name: 'Asd20PrintContents',
|
|
30
|
+
props: {
|
|
31
|
+
headings: { type: Array, default: () => [] },
|
|
32
|
+
label: { type: String, default: 'Contents' },
|
|
33
|
+
},
|
|
34
|
+
computed: {
|
|
35
|
+
entries() { return this.headings.filter(heading => heading.id && heading.label) },
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<style>
|
|
41
|
+
.asd20-print-only { display: none; }
|
|
42
|
+
</style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<header class="asd20-print-header asd20-print-only">
|
|
3
|
+
<h1 v-if="title">
|
|
4
|
+
{{ title }}
|
|
5
|
+
</h1>
|
|
6
|
+
<p v-if="organizationTitle">
|
|
7
|
+
{{ organizationTitle }}
|
|
8
|
+
</p>
|
|
9
|
+
<p v-if="academicYear">
|
|
10
|
+
Academic year: {{ academicYear }}
|
|
11
|
+
</p>
|
|
12
|
+
<p v-if="editorialRevisedAt">
|
|
13
|
+
Content revised: {{ editorialRevisedAt }}
|
|
14
|
+
</p>
|
|
15
|
+
<p
|
|
16
|
+
data-asd20-print-generated-at
|
|
17
|
+
:data-asd20-print-fixed-generated-at="generatedAt || undefined"
|
|
18
|
+
>
|
|
19
|
+
Generated: {{ formattedGeneratedAt }}
|
|
20
|
+
</p>
|
|
21
|
+
<p v-if="canonicalUrl">
|
|
22
|
+
The online document remains authoritative:
|
|
23
|
+
<a :href="canonicalUrl">{{ canonicalUrl }}</a>
|
|
24
|
+
</p>
|
|
25
|
+
</header>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
export default {
|
|
30
|
+
name: 'Asd20PrintHeader',
|
|
31
|
+
props: {
|
|
32
|
+
// Leave empty when the host already owns its document H1.
|
|
33
|
+
title: { type: String, default: '' },
|
|
34
|
+
organizationTitle: { type: String, default: '' },
|
|
35
|
+
academicYear: { type: String, default: '' },
|
|
36
|
+
editorialRevisedAt: { type: String, default: '' },
|
|
37
|
+
generatedAt: { type: String, default: '' },
|
|
38
|
+
canonicalUrl: { type: String, default: '' },
|
|
39
|
+
},
|
|
40
|
+
computed: {
|
|
41
|
+
formattedGeneratedAt() {
|
|
42
|
+
if (!this.generatedAt) return ''
|
|
43
|
+
const date = new Date(this.generatedAt)
|
|
44
|
+
return Number.isNaN(date.getTime()) ? this.generatedAt : new Intl.DateTimeFormat('en-US', {
|
|
45
|
+
timeZone: 'America/Denver', dateStyle: 'medium', timeStyle: 'long',
|
|
46
|
+
}).format(date)
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
.asd20-print-only { display: none; }
|
|
54
|
+
</style>
|