@asd20/ui-next 2.30.1 → 2.31.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.
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <div
3
+ class="asd20-handbook-document"
4
+ :data-asd20-document-root="documentId"
5
+ >
6
+ <header>
7
+ <h1>{{ title }}</h1>
8
+ <div
9
+ v-if="document.introduction.lead"
10
+ class="asd20-page-header__lead"
11
+ v-html="document.introduction.lead"
12
+ />
13
+ </header>
14
+ <asd20-print-header
15
+ :organization-title="organization.title || ''"
16
+ :canonical-url="documentUrl"
17
+ v-bind="printMetadata"
18
+ />
19
+ <asd20-print-contents :headings="document.headings" />
20
+ <main>
21
+ <article
22
+ v-for="entry in document.entries"
23
+ :key="entry.key"
24
+ class="asd20-template-handbook__topic"
25
+ :data-topic-key="entry.key"
26
+ :data-message-id="entry.message.id"
27
+ >
28
+ <h2 :id="entry.titleId">
29
+ {{ entry.message.heading || entry.message.title }}
30
+ </h2>
31
+ <div
32
+ v-if="entry.message.shortDescription || entry.message.summary"
33
+ class="asd20-template-handbook__summary"
34
+ v-html="entry.message.shortDescription || entry.message.summary"
35
+ />
36
+ <asd20-messaging
37
+ v-bind="entry.message"
38
+ heading=""
39
+ short-description=""
40
+ detail-link=""
41
+ :heading-level-offset="1"
42
+ :heading-id-prefix="entry.key"
43
+ />
44
+ </article>
45
+ </main>
46
+ </div>
47
+ </template>
48
+
49
+ <script>
50
+ import Asd20Messaging from '../../atoms/Asd20Messaging'
51
+ import Asd20PrintHeader from '../../molecules/Asd20PrintHeader'
52
+ import Asd20PrintContents from '../../molecules/Asd20PrintContents'
53
+ import { buildHandbookDocument } from '../../../helpers/handbookDocument'
54
+ import { publicationHeadingPlanKey } from '../../atoms/Asd20RichBodyContent/headingContext'
55
+
56
+ export default {
57
+ name: 'Asd20HandbookDocument',
58
+ components: { Asd20Messaging, Asd20PrintHeader, Asd20PrintContents },
59
+ provide() { return { [publicationHeadingPlanKey]: () => this.document.headingModels.idPlan } },
60
+ props: {
61
+ title: { type: String, required: true },
62
+ messages: { type: Array, required: true },
63
+ metaDescription: { type: String, default: '' },
64
+ organization: { type: Object, default: () => ({}) },
65
+ documentId: { type: String, required: true },
66
+ documentUrl: { type: String, required: true },
67
+ printMetadata: { type: Object, default: () => ({}) },
68
+ },
69
+ computed: {
70
+ document() { return buildHandbookDocument(this.$props) },
71
+ },
72
+ }
73
+ </script>
74
+
75
+ <style lang="scss">
76
+ @use '../../../design/document-print';
77
+ .asd20-handbook-document {
78
+ font-family: Arial, sans-serif;
79
+ h1 { font-size: 22pt; }
80
+ h2 { font-size: 16pt; }
81
+ h3 { font-size: 13pt; }
82
+ h4, h5, h6 { font-size: 11pt; }
83
+ a { color: #111; }
84
+ }
85
+ </style>
@@ -1,8 +1,12 @@
1
1
  <template>
2
- <div class="asd20-template-handbook">
2
+ <div
3
+ class="asd20-template-handbook"
4
+ :data-asd20-document-root="documentPrint ? publicationDocumentId : undefined"
5
+ >
3
6
  <asd20-skip-to to="#handbook-content" />
4
7
 
5
8
  <asd20-site-navigation
9
+ :data-asd20-print-hide="documentPrint ? '' : undefined"
6
10
  v-bind="siteNavigationSearchProps"
7
11
  :navigation="navigation"
8
12
  :action-items="actionItems"
@@ -15,6 +19,7 @@
15
19
  />
16
20
 
17
21
  <asd20-notification-group
22
+ :data-asd20-print-hide="documentPrint ? '' : undefined"
18
23
  :notifications="activeNotificationsByType.banner"
19
24
  group-type="banner"
20
25
  @dismiss="$emit('dismiss-notification', $event)"
@@ -42,7 +47,18 @@
42
47
  </template>
43
48
  </asd20-page-header>
44
49
 
50
+ <slot name="document-actions" />
51
+
52
+ <asd20-print-header
53
+ v-if="documentPrint"
54
+ :organization-title="organization?.title || organization?.name || ''"
55
+ :canonical-url="publicationDocumentUrl"
56
+ v-bind="printMetadata"
57
+ title=""
58
+ />
59
+
45
60
  <asd20-notification-group
61
+ :data-asd20-print-hide="documentPrint ? '' : undefined"
46
62
  :notifications="activeNotificationsByType.inline"
47
63
  group-type="inline"
48
64
  @dismiss="$emit('dismiss-notification', $event)"
@@ -67,34 +83,34 @@
67
83
  >
68
84
  Topics
69
85
  </button>
70
- <h2 class="asd20-template-handbook__index-heading">
86
+ <div class="asd20-template-handbook__index-heading">
71
87
  Topics
72
- </h2>
88
+ </div>
73
89
  <ol
74
90
  id="handbook-topics"
75
91
  :class="{ 'is-open': topicsOpen }"
76
92
  >
77
93
  <li
78
- v-for="message in topicMessages"
79
- :key="message.id"
94
+ v-for="entry in topicEntries"
95
+ :key="entry.key"
80
96
  >
81
97
  <div class="asd20-template-handbook__topic-row">
82
98
  <button
83
99
  class="asd20-template-handbook__topic-button"
84
100
  type="button"
85
- :aria-current="activeMessage && activeMessage.id === message.id ? 'true' : null"
86
- @click="selectTopic(message.id)"
101
+ :aria-current="activeEntry && activeEntry.key === entry.key ? 'true' : null"
102
+ @click="selectTopic(entry.key)"
87
103
  >
88
- {{ message.heading || message.title }}
104
+ {{ entry.message.heading || entry.message.title }}
89
105
  </button>
90
106
  <button
91
- v-if="subtopicsFor(message.id).length"
107
+ v-if="subtopicsFor(entry.key).length"
92
108
  class="asd20-template-handbook__expand-button"
93
109
  type="button"
94
- :aria-expanded="expandedTopicId === message.id"
95
- :aria-controls="`handbook-subtopics-${message.id}`"
96
- :aria-label="`${expandedTopicId === message.id ? 'Hide' : 'Show'} sections in ${message.heading || message.title}`"
97
- @click="toggleTopic(message.id)"
110
+ :aria-expanded="expandedTopicId === entry.key"
111
+ :aria-controls="entry.subtopicsId"
112
+ :aria-label="`${expandedTopicId === entry.key ? 'Hide' : 'Show'} sections in ${entry.message.heading || entry.message.title}`"
113
+ @click="toggleTopic(entry.key)"
98
114
  >
99
115
  <asd20-icon
100
116
  name="chevron"
@@ -104,19 +120,19 @@
104
120
  </button>
105
121
  </div>
106
122
  <ol
107
- v-if="subtopicsFor(message.id).length"
108
- v-show="expandedTopicId === message.id"
109
- :id="`handbook-subtopics-${message.id}`"
123
+ v-if="subtopicsFor(entry.key).length"
124
+ v-show="expandedTopicId === entry.key"
125
+ :id="entry.subtopicsId"
110
126
  class="asd20-template-handbook__subtopics"
111
127
  >
112
128
  <li
113
- v-for="subtopic in subtopicsFor(message.id)"
114
- :key="subtopic.index"
129
+ v-for="subtopic in subtopicsFor(entry.key)"
130
+ :key="subtopic.id"
115
131
  >
116
132
  <button
117
133
  class="asd20-template-handbook__subtopic-button"
118
134
  type="button"
119
- @click="selectSubtopic(message.id, subtopic.index)"
135
+ @click="selectSubtopic(entry.key, subtopic.id)"
120
136
  >
121
137
  {{ subtopic.label }}
122
138
  </button>
@@ -126,30 +142,46 @@
126
142
  </ol>
127
143
  </nav>
128
144
 
145
+ <p
146
+ v-if="publicationDiagnostics.length"
147
+ class="asd20-template-handbook__publication-review"
148
+ role="status"
149
+ >
150
+ This handbook has content that needs publication review before PDF export.
151
+ </p>
152
+
153
+ <asd20-print-contents
154
+ v-if="documentPrint"
155
+ :headings="printHeadings"
156
+ />
157
+
129
158
  <article
130
- v-for="message in topicMessages"
131
- :key="message.id"
132
- :data-message-id="message.id"
159
+ v-for="entry in topicEntries"
160
+ :key="entry.key"
161
+ :data-message-id="entry.message.id"
162
+ :data-topic-key="entry.key"
133
163
  class="asd20-template-handbook__topic"
134
- :class="{ 'is-active': activeMessage && activeMessage.id === message.id }"
164
+ :class="{ 'is-active': activeEntry && activeEntry.key === entry.key }"
135
165
  >
136
- <h2>
137
- {{ message.heading || message.title }}
166
+ <h2 :id="entry.titleId">
167
+ {{ entry.message.heading || entry.message.title }}
138
168
  </h2>
139
169
  <div
140
- v-if="topicSummary(message)"
170
+ v-if="topicSummary(entry.message)"
141
171
  class="asd20-template-handbook__summary"
142
- v-html="topicSummary(message)"
172
+ v-html="topicSummary(entry.message)"
143
173
  />
144
174
  <hr
145
- v-if="topicSummary(message)"
175
+ v-if="topicSummary(entry.message)"
146
176
  class="asd20-template-handbook__summary-divider"
147
177
  />
148
178
  <asd20-messaging
149
- v-bind="message"
179
+ v-bind="entry.message"
150
180
  heading=""
151
181
  short-description=""
152
182
  detail-link=""
183
+ :heading-level-offset="1"
184
+ :heading-id-prefix="entry.key"
153
185
  />
154
186
  </article>
155
187
  <p v-if="!topicMessages.length">
@@ -177,7 +209,12 @@ import Asd20SiteNavigation from '../../../components/organisms/Asd20SiteNavigati
177
209
  import Asd20NotificationGroup from '../../../components/organisms/Asd20NotificationGroup'
178
210
  import Asd20QuicklinksMenu from '../../../components/organisms/Asd20QuicklinksMenu'
179
211
  import Asd20PageFooter from '../../../components/organisms/Asd20PageFooter'
212
+ import Asd20PrintHeader from '../../molecules/Asd20PrintHeader'
213
+ import Asd20PrintContents from '../../molecules/Asd20PrintContents'
214
+ import { buildHandbookDocument } from '../../../helpers/handbookDocument'
215
+ import { installDocumentPrintLifecycle } from '../../../helpers/documentPrint'
180
216
  import pageTemplateMixin from '../../../mixins/pageTemplateMixin'
217
+ import { publicationHeadingPlanKey } from '../../atoms/Asd20RichBodyContent/headingContext'
181
218
 
182
219
  export default {
183
220
  name: 'Asd20HandbookTemplate',
@@ -190,63 +227,71 @@ export default {
190
227
  Asd20NotificationGroup,
191
228
  Asd20QuicklinksMenu,
192
229
  Asd20PageFooter,
230
+ Asd20PrintHeader,
231
+ Asd20PrintContents,
193
232
  },
194
233
  mixins: [pageTemplateMixin],
234
+ provide() {
235
+ return { [publicationHeadingPlanKey]: () => this.publicationHeadings.idPlan }
236
+ },
237
+ props: {
238
+ documentPrint: { type: Boolean, default: false },
239
+ printMetadata: { type: Object, default: () => ({}) },
240
+ publicationDocumentId: { type: String, default: 'handbook' },
241
+ publicationDocumentUrl: { type: String, default: '' },
242
+ publicationReservedIds: { type: Array, default: () => [] },
243
+ },
195
244
  data: () => ({
196
245
  selectedMessageId: null,
197
246
  expandedTopicId: null,
198
- subtopicsByMessageId: {},
199
247
  topicsOpen: false,
200
248
  }),
201
249
  computed: {
202
- handbookLead() {
203
- return this.firstMessage?.shortDescription || this.firstMessage?.summary || this.metaDescription
250
+ handbookDocument() {
251
+ return buildHandbookDocument({
252
+ messages: this.messages, metaDescription: this.metaDescription,
253
+ documentId: this.publicationDocumentId, documentUrl: this.publicationDocumentUrl,
254
+ reservedIds: this.publicationReservedIds,
255
+ })
204
256
  },
205
- topicMessages() {
206
- return this.messages.slice(1)
257
+ introduction() { return this.handbookDocument.introduction },
258
+ publicationDiagnostics() { return this.handbookDocument.diagnostics },
259
+ printHeadings() { return this.handbookDocument.headings },
260
+ handbookLead() { return this.introduction.lead },
261
+ topicMessages() { return this.messages.slice(1) },
262
+ topicEntries() { return this.handbookDocument.entries },
263
+ publicationHeadings() { return this.handbookDocument.headingModels },
264
+ activeEntry() {
265
+ return this.topicEntries.find(entry => entry.key === this.selectedMessageId)
266
+ || this.topicEntries[0] || null
207
267
  },
208
268
  activeMessage() {
209
- return this.topicMessages.find(message => message.id === this.selectedMessageId)
210
- || this.topicMessages[0]
211
- || null
269
+ return this.activeEntry?.message || null
212
270
  },
213
271
  },
214
272
  watch: {
215
- messages: {
216
- handler() {
217
- this.$nextTick(() => this.refreshSubtopics())
218
- },
219
- deep: true,
220
- },
221
- },
222
- mounted() {
223
- this.$nextTick(() => this.refreshSubtopics())
273
+ documentPrint() { this.updatePrintLifecycle() },
274
+ messages: { deep: true, handler() { this.updatePrintLifecycle() } },
275
+ publicationDocumentId() { this.updatePrintLifecycle() },
224
276
  },
277
+ mounted() { this.updatePrintLifecycle() },
278
+ beforeUnmount() { this.stopPrintLifecycle?.() },
225
279
  methods: {
280
+ updatePrintLifecycle() {
281
+ this.stopPrintLifecycle?.()
282
+ this.stopPrintLifecycle = this.documentPrint ? installDocumentPrintLifecycle(this.$el) : null
283
+ },
226
284
  topicSummary(message) {
227
285
  return message.shortDescription || message.summary || ''
228
286
  },
229
- topicArticle(messageId) {
287
+ topicArticle(topicKey) {
230
288
  return Array.from(this.$el.querySelectorAll('.asd20-template-handbook__topic'))
231
- .find(article => article.dataset.messageId === String(messageId))
289
+ .find(article => article.dataset.topicKey === topicKey)
232
290
  },
233
- subtopicsFor(messageId) {
234
- return this.subtopicsByMessageId[messageId] || []
235
- },
236
- refreshSubtopics() {
237
- const nextSubtopics = {}
238
- this.topicMessages.forEach(message => {
239
- const article = this.topicArticle(message.id)
240
- if (!article) return
241
- const headings = Array.from(article.querySelectorAll('.asd20-rich-body-content h2'))
242
- const subtopics = headings
243
- .map((heading, index) => ({ index, label: heading.textContent.trim() }))
244
- .filter(subtopic => subtopic.label)
245
- if (subtopics.length) nextSubtopics[message.id] = subtopics
246
- })
247
- if (JSON.stringify(nextSubtopics) !== JSON.stringify(this.subtopicsByMessageId)) {
248
- this.subtopicsByMessageId = nextSubtopics
249
- }
291
+ subtopicsFor(topicKey) {
292
+ const index = this.topicEntries.findIndex(entry => entry.key === topicKey)
293
+ return this.publicationHeadings.models[index]?.headings
294
+ .filter(heading => heading.authoredLevel === 2 && heading.label) || []
250
295
  },
251
296
  toggleTopic(messageId) {
252
297
  this.expandedTopicId = this.expandedTopicId === messageId ? null : messageId
@@ -258,14 +303,21 @@ export default {
258
303
  await this.$nextTick()
259
304
  this.topicArticle(messageId)?.scrollIntoView({ block: 'start' })
260
305
  },
261
- async selectSubtopic(messageId, headingIndex) {
306
+ async selectSubtopic(messageId, headingId) {
262
307
  this.selectedMessageId = messageId
263
308
  this.expandedTopicId = messageId
264
309
  this.topicsOpen = false
265
310
  await this.$nextTick()
266
311
  const article = this.topicArticle(messageId)
267
- const headings = article?.querySelectorAll('.asd20-rich-body-content h2')
268
- headings?.[headingIndex]?.scrollIntoView({ block: 'start' })
312
+ const heading = Array.from(article?.querySelectorAll('[id]') || [])
313
+ .find(element => element.id === headingId)
314
+ const toggle = heading?.closest('.asd20-accordion')
315
+ ?.querySelector('[aria-expanded="false"][aria-controls]')
316
+ if (toggle) {
317
+ toggle.click()
318
+ await this.$nextTick()
319
+ }
320
+ heading?.scrollIntoView({ block: 'start' })
269
321
  },
270
322
  },
271
323
  }
@@ -411,7 +463,7 @@ export default {
411
463
  scroll-margin-top: space(2);
412
464
  }
413
465
 
414
- &__topic .asd20-rich-body-content h2 {
466
+ &__topic .asd20-rich-body-content h3 {
415
467
  scroll-margin-top: space(2);
416
468
  }
417
469
 
@@ -0,0 +1,104 @@
1
+ // Additive overrides: every content rule requires BOTH markers. The legacy
2
+ // _print.scss rules remain intact, including its existing unnamed page margin.
3
+ @media print {
4
+ @page asd20-document {
5
+ size: letter portrait;
6
+ margin: 0.5in;
7
+ @bottom-center {
8
+ content: 'Page ' counter(page) ' of ' counter(pages);
9
+ font-family: Arial, sans-serif;
10
+ font-size: 9pt;
11
+ color: #111;
12
+ }
13
+ }
14
+
15
+ html[data-asd20-print-profile='document'] [data-asd20-document-root] {
16
+ page: asd20-document;
17
+ display: block !important;
18
+ color: #111 !important;
19
+ font-size: 10pt !important;
20
+ line-height: 1.45 !important;
21
+ print-color-adjust: exact;
22
+ -webkit-print-color-adjust: exact;
23
+
24
+ [data-asd20-print-hide], .asd20-print-hide,
25
+ .asd20-template-handbook__index, .asd20-accordion__header button,
26
+ .asd20-accordion__header .asd20-badge,
27
+ .asd20-message-video__frame, .asd20-tableau-embed__frame,
28
+ iframe, video, tableau-viz {
29
+ display: none !important;
30
+ }
31
+
32
+ .asd20-print-only { display: block !important; }
33
+ .asd20-print-contents__label { font-weight: bold; }
34
+ .asd20-print-contents li { break-inside: avoid; }
35
+ .asd20-print-contents ol { list-style: none; margin-left: 0; padding-left: 0; }
36
+ .asd20-print-contents a { display: flex; justify-content: space-between; gap: 1em; }
37
+ .asd20-print-contents__page { flex: 0 0 5ch; text-align: right; font-variant-numeric: tabular-nums; }
38
+ .asd20-handbook-document > header h1 { margin-top: 0 !important; }
39
+
40
+ .asd20-template-handbook__layout,
41
+ .asd20-template-handbook__topic,
42
+ .asd20-messaging, .asd20-messaging__content,
43
+ .asd20-accordion__content, .asd20-accordion__content-inner,
44
+ .asd20-accordion__viewport, .tableWrapper {
45
+ display: block !important;
46
+ position: static !important;
47
+ overflow: visible !important;
48
+ transform: none !important;
49
+ height: auto !important;
50
+ max-height: none !important;
51
+ min-height: 0 !important;
52
+ opacity: 1 !important;
53
+ break-inside: auto !important;
54
+ transition: none !important;
55
+ }
56
+
57
+ .asd20-template-handbook__topic { margin: 1.5rem 0 !important; }
58
+ h1, h2, h3, h4, h5, h6, caption, .asd20-accordion__header {
59
+ break-after: avoid-page;
60
+ break-inside: avoid-page;
61
+ }
62
+ p { orphans: 3; widows: 3; }
63
+ blockquote { break-inside: auto; }
64
+ .asd20-print-keep { break-inside: avoid-page !important; }
65
+ .asd20-print-break-before { break-before: page; }
66
+
67
+ img, .message-image {
68
+ display: block !important;
69
+ max-width: 100% !important;
70
+ height: auto !important;
71
+ object-fit: contain !important;
72
+ break-inside: avoid-page;
73
+ }
74
+ .asd20-message-image-gallery__images {
75
+ display: block !important;
76
+ overflow: visible !important;
77
+ max-height: none !important;
78
+ }
79
+ .asd20-message-image-gallery__images > img { max-width: 100% !important; max-height: none !important; }
80
+ table {
81
+ width: 100% !important;
82
+ min-width: 0 !important;
83
+ table-layout: fixed;
84
+ border-collapse: collapse;
85
+ break-inside: auto;
86
+ font-size: 9pt;
87
+ }
88
+ thead { display: table-header-group; }
89
+ tfoot { display: table-footer-group; }
90
+ tr { break-inside: avoid-page; }
91
+ th, td { min-width: 0 !important; overflow-wrap: anywhere; padding: 0.3em !important; border: 1px solid #888; }
92
+ th { background: #eee !important; color: #111 !important; }
93
+ a { overflow-wrap: anywhere; text-decoration: underline !important; break-inside: auto; }
94
+ a[href='/'] { display: inline !important; }
95
+ a::after { content: none !important; display: none !important; }
96
+ a[data-asd20-print-source]::after {
97
+ content: ' (' attr(data-asd20-print-source) ')' !important;
98
+ display: inline !important;
99
+ font-size: 9pt;
100
+ }
101
+ pre { white-space: pre-wrap; overflow-wrap: anywhere; }
102
+ ul, ol { columns: auto !important; }
103
+ }
104
+ }
@@ -1,3 +1,5 @@
1
+ @use './document-print';
2
+
1
3
  @media print {
2
4
  @page {
3
5
  margin: 0.5in;