@asd20/ui-next 2.15.1 → 2.16.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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ # [2.16.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.15.1...ui-next-v2.16.0) (2026-08-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * support canonical TipTap tables ([179eca3](https://github.com/academydistrict20/asd20-ui-next/commit/179eca3f7c0e0f2f226c8d660d84e3564fbf28c2))
9
+
3
10
  ## [2.15.1](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.15.0...ui-next-v2.15.1) (2026-07-31)
4
11
 
5
12
  # [2.15.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.14.1...ui-next-v2.15.0) (2026-07-31)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.15.1",
3
+ "version": "2.16.0",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -114,5 +114,48 @@ export default {
114
114
  &__html {
115
115
  display: block;
116
116
  }
117
+
118
+ :deep(.tableWrapper) {
119
+ max-width: 100%;
120
+ margin: 0.75rem 0;
121
+ overflow-x: auto;
122
+ overscroll-behavior-inline: contain;
123
+ }
124
+
125
+ :deep(.asd20-message-table) {
126
+ border-collapse: collapse;
127
+ font-size: 0.875rem;
128
+ }
129
+
130
+ :deep(.asd20-message-table th),
131
+ :deep(.asd20-message-table td) {
132
+ min-width: 3rem;
133
+ padding: 0.5rem;
134
+ border-bottom: 1px solid #cfd8e3;
135
+ vertical-align: top;
136
+ }
137
+
138
+ :deep(.asd20-message-table th) {
139
+ color: white;
140
+ background: #003b71;
141
+ font-weight: 600;
142
+ }
143
+
144
+ :deep(.asd20-message-table caption) {
145
+ padding: 0 0 0.5rem;
146
+ color: #263238;
147
+ font-weight: 600;
148
+ text-align: left;
149
+ }
150
+
151
+ :deep(.asd20-message-table th > :first-child),
152
+ :deep(.asd20-message-table td > :first-child) {
153
+ margin-top: 0;
154
+ }
155
+
156
+ :deep(.asd20-message-table th > :last-child),
157
+ :deep(.asd20-message-table td > :last-child) {
158
+ margin-bottom: 0;
159
+ }
117
160
  }
118
161
  </style>
@@ -55,6 +55,7 @@ const MARK_TYPES = new Set([
55
55
  'subscript',
56
56
  'superscript',
57
57
  ])
58
+ const TABLE_HEADER_SCOPES = new Set(['col', 'row', 'colgroup', 'rowgroup'])
58
59
  const MARK_RENDER_ORDER = Object.freeze({
59
60
  link: 0,
60
61
  bold: 1,
@@ -78,10 +79,10 @@ const NODE_ATTRIBUTE_NAMES = Object.freeze({
78
79
  listItem: [],
79
80
  image: ['src', 'alt', 'title', 'width', 'height'],
80
81
  videoEmbed: ['src', 'title', 'descriptiveText'],
81
- table: [],
82
+ table: ['caption'],
82
83
  tableRow: [],
83
84
  tableCell: ['colspan', 'rowspan', 'colwidth', 'align'],
84
- tableHeader: ['colspan', 'rowspan', 'colwidth', 'align'],
85
+ tableHeader: ['colspan', 'rowspan', 'colwidth', 'align', 'scope'],
85
86
  tableauBlock: [
86
87
  'blockId',
87
88
  'src',
@@ -406,6 +407,15 @@ function validateNode(node, path) {
406
407
  return validateAtom(node, path)
407
408
  }
408
409
  if (node.type === 'table') {
410
+ const captionIssue = validateStringAttribute(node, 'caption', path)
411
+ if (captionIssue) return captionIssue
412
+ if (
413
+ node.attrs?.caption !== undefined &&
414
+ node.attrs?.caption !== null &&
415
+ !node.attrs.caption.trim()
416
+ ) {
417
+ return issue('malformed-attribute', path, 'caption')
418
+ }
409
419
  return validateChildren(node, path, new Set(['tableRow']), true)
410
420
  }
411
421
  if (node.type === 'tableRow') {
@@ -421,6 +431,7 @@ function validateNode(node, path) {
421
431
  const rowspan = node.attrs?.rowspan ?? 1
422
432
  const colwidth = node.attrs?.colwidth
423
433
  const align = node.attrs?.align
434
+ const scope = node.attrs?.scope
424
435
  if (!Number.isInteger(colspan) || colspan < 1) {
425
436
  return issue('malformed-attribute', path, 'colspan')
426
437
  }
@@ -443,6 +454,14 @@ function validateNode(node, path) {
443
454
  ) {
444
455
  return issue('malformed-attribute', path, 'align')
445
456
  }
457
+ if (
458
+ node.type === 'tableHeader' &&
459
+ scope !== undefined &&
460
+ scope !== null &&
461
+ !TABLE_HEADER_SCOPES.has(scope)
462
+ ) {
463
+ return issue('malformed-attribute', path, 'scope')
464
+ }
446
465
  return validateChildren(node, path, STANDARD_BLOCK_NODE_TYPES, true)
447
466
  }
448
467
  if (node.type === 'tableauBlock') {
@@ -563,14 +582,15 @@ function renderTableCell(node, tagName) {
563
582
  const rowspan = node.attrs?.rowspan ?? 1
564
583
  const colwidth = node.attrs?.colwidth
565
584
  const align = node.attrs?.align
585
+ const scope = node.attrs?.scope
566
586
  return `<${tagName}${attribute('colspan', colspan)}${attribute(
567
587
  'rowspan',
568
588
  rowspan
569
589
  )}${
570
590
  Array.isArray(colwidth) ? attribute('colwidth', colwidth.join(',')) : ''
571
- }${align ? attribute('style', `text-align: ${align}`) : ''}>${renderChildren(
572
- node
573
- )}</${tagName}>`
591
+ }${align ? attribute('style', `text-align: ${align}`) : ''}${
592
+ scope ? attribute('scope', scope) : ''
593
+ }>${renderChildren(node)}</${tagName}>`
574
594
  }
575
595
 
576
596
  function renderTable(node) {
@@ -596,12 +616,15 @@ function renderTable(node) {
596
616
  : `<col style="min-width: ${minimumWidth}px;">`
597
617
  )
598
618
  .join('')
599
- return `<table${attribute(
619
+ const caption = node.attrs?.caption
620
+ ? `<caption>${escapeHtml(node.attrs.caption)}</caption>`
621
+ : ''
622
+ return `<div class="tableWrapper"><table class="asd20-message-table"${attribute(
600
623
  'style',
601
624
  tableStyle
602
- )}><colgroup>${colgroup}</colgroup><tbody>${renderChildren(
625
+ )}>${caption}<colgroup>${colgroup}</colgroup><tbody>${renderChildren(
603
626
  node
604
- )}</tbody></table>`
627
+ )}</tbody></table></div>`
605
628
  }
606
629
 
607
630
  function renderStandardNode(node) {