@tiptap/extension-table 3.27.4 → 3.29.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/dist/cell/index.cjs +26 -1
- package/dist/cell/index.cjs.map +1 -1
- package/dist/cell/index.js +26 -1
- package/dist/cell/index.js.map +1 -1
- package/dist/header/index.cjs +26 -1
- package/dist/header/index.cjs.map +1 -1
- package/dist/header/index.js +26 -1
- package/dist/header/index.js.map +1 -1
- package/dist/index.cjs +38 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -3
- package/dist/index.js.map +1 -1
- package/dist/kit/index.cjs +38 -3
- package/dist/kit/index.cjs.map +1 -1
- package/dist/kit/index.js +38 -3
- package/dist/kit/index.js.map +1 -1
- package/dist/table/index.cjs +3 -1
- package/dist/table/index.cjs.map +1 -1
- package/dist/table/index.js +3 -1
- package/dist/table/index.js.map +1 -1
- package/package.json +5 -5
- package/src/cell/table-cell.ts +10 -1
- package/src/header/table-header.ts +10 -1
- package/src/table/utilities/markdown.ts +8 -1
- package/src/utils/fillEmptyCellContent.ts +25 -0
package/dist/cell/index.cjs
CHANGED
|
@@ -75,6 +75,23 @@ function parseColwidth(element) {
|
|
|
75
75
|
return parseColgroupWidth(element);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// src/utils/fillEmptyCellContent.ts
|
|
79
|
+
var COLLAPSIBLE_WHITESPACE = /[ \t\r\n\f]+/g;
|
|
80
|
+
function isEmptyCellElement(element) {
|
|
81
|
+
var _a;
|
|
82
|
+
if (element.children.length > 0) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return ((_a = element.textContent) != null ? _a : "").replace(COLLAPSIBLE_WHITESPACE, "") === "";
|
|
86
|
+
}
|
|
87
|
+
function fillEmptyCellContent(cellType) {
|
|
88
|
+
const filled = cellType.createAndFill();
|
|
89
|
+
if (!filled) {
|
|
90
|
+
throw new Error(`[tiptap error]: "${cellType.name}" has no default content to backfill.`);
|
|
91
|
+
}
|
|
92
|
+
return filled.content;
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
// src/cell/table-cell.ts
|
|
79
96
|
var TableCell = import_core.Node.create({
|
|
80
97
|
name: "tableCell",
|
|
@@ -102,7 +119,15 @@ var TableCell = import_core.Node.create({
|
|
|
102
119
|
tableRole: "cell",
|
|
103
120
|
isolating: true,
|
|
104
121
|
parseHTML() {
|
|
105
|
-
return [
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
// Backfill empty cells; non-empty cells fall through to the rule below.
|
|
125
|
+
tag: "td",
|
|
126
|
+
getAttrs: (node) => isEmptyCellElement(node) ? {} : false,
|
|
127
|
+
getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name])
|
|
128
|
+
},
|
|
129
|
+
{ tag: "td" }
|
|
130
|
+
];
|
|
106
131
|
},
|
|
107
132
|
renderHTML({ HTMLAttributes }) {
|
|
108
133
|
return ["td", (0, import_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes), 0];
|
package/dist/cell/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cell/index.ts","../../src/cell/table-cell.ts","../../src/utils/parseAlign.ts","../../src/utils/parseColwidth.ts"],"sourcesContent":["export * from './table-cell.js'\n","import '../types.js'\n\nimport { mergeAttributes, Node } from '@tiptap/core'\n\nimport { createAlignAttribute } from '../utils/parseAlign.js'\nimport { parseColwidth } from '../utils/parseColwidth.js'\n\nexport interface TableCellOptions {\n /**\n * The HTML attributes for a table cell node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\n/**\n * This extension allows you to create table cells.\n * @see https://www.tiptap.dev/api/nodes/table-cell\n */\nexport const TableCell = Node.create<TableCellOptions>({\n name: 'tableCell',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n content: 'block+',\n\n addAttributes() {\n return {\n colspan: {\n default: 1,\n },\n rowspan: {\n default: 1,\n },\n colwidth: {\n default: null,\n parseHTML: parseColwidth,\n },\n align: createAlignAttribute(),\n }\n },\n\n tableRole: 'cell',\n\n isolating: true,\n\n parseHTML() {\n return [{ tag: 'td' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['td', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n})\n","import type { Attribute } from '@tiptap/core'\n\n/**\n * Supported table cell alignment values\n */\nexport enum TableCellAlign {\n Left = 'left',\n Right = 'right',\n Center = 'center',\n}\n\n/**\n * Normalize unknown input into a supported table alignment\n *\n * @param value - A potential alignment value\n * @returns A valid TableCellAlign value or null\n */\nexport function normalizeTableCellAlign(value: unknown): TableCellAlign | null {\n if (\n value === TableCellAlign.Left ||\n value === TableCellAlign.Right ||\n value === TableCellAlign.Center\n ) {\n return value\n }\n\n return null\n}\n\n/**\n * Parse table cell alignment from an HTML element\n *\n * Prefers inline style (${\"`\"}text-align${\"`\"}) and falls back to the legacy\n * ${\"`\"}align${\"`\"} attribute.\n *\n * @param element - The table cell/header DOM element\n * @returns A valid TableCellAlign value or null\n */\nexport function parseAlign(element: HTMLElement): TableCellAlign | null {\n const styleAlign = (element.style.textAlign || '').trim().toLowerCase()\n const attrAlign = (element.getAttribute('align') || '').trim().toLowerCase()\n const align = styleAlign || attrAlign\n\n return normalizeTableCellAlign(align)\n}\n\n/**\n * Normalize alignment from a generic attrs object that may include an align field\n *\n * @param attributes - A node attrs-like object with an optional align field\n * @returns A valid TableCellAlign value or null.\n */\nexport function normalizeTableCellAlignFromAttributes(\n attributes: { align?: TableCellAlign } | null | undefined,\n): TableCellAlign | null {\n return normalizeTableCellAlign(attributes?.align)\n}\n\n/**\n * Create a reusable Tiptap attribute config for table alignment\n *\n * @returns A Tiptap Attribute definition that parses and renders table alignment\n */\nexport function createAlignAttribute(): Attribute {\n return {\n default: null,\n parseHTML: (element: HTMLElement) => parseAlign(element),\n renderHTML: (attributes: { align?: TableCellAlign | null }) => {\n if (!attributes.align) {\n return {}\n }\n\n return {\n style: `text-align: ${attributes.align}`,\n }\n },\n }\n}\n","/**\n * reads the width of the `<col>` element matching a cell's column from the table's `<colgroup>`\n *\n * @param element - The table cell/header DOM element\n * @returns - An array with the column width in pixels or null\n */\nfunction parseColgroupWidth(element: HTMLElement): number[] | null {\n const row = element.parentElement\n const table = element.closest('table')\n\n if (!row || !table) {\n return null\n }\n\n const cellIndex = Array.from(row.children).indexOf(element)\n const width = table.querySelectorAll('colgroup > col')[cellIndex]?.getAttribute('width')\n\n return width ? [parseInt(width, 10)] : null\n}\n\n/**\n * Parse the column width/s of a table cell/header from an HTML element.\n * it prefers the `colwidth` attribute and if not-provided falls back to the `width` attribute\n * of the matching `<col>` element in the table's `<colgroup>`.\n *\n * @param element - The table cell/header DOM element\n * @returns - An array of column widths in pixels or null\n */\nexport function parseColwidth(element: HTMLElement): number[] | null {\n const colwidth = element.getAttribute('colwidth')\n\n if (colwidth) {\n return colwidth.split(',').map(width => parseInt(width, 10))\n }\n\n return parseColgroupWidth(element)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,kBAAsC;;;ACe/B,SAAS,wBAAwB,OAAuC;AAC7E,MACE,UAAU,qBACV,UAAU,uBACV,UAAU,uBACV;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAWO,SAAS,WAAW,SAA6C;AACtE,QAAM,cAAc,QAAQ,MAAM,aAAa,IAAI,KAAK,EAAE,YAAY;AACtE,QAAM,aAAa,QAAQ,aAAa,OAAO,KAAK,IAAI,KAAK,EAAE,YAAY;AAC3E,QAAM,QAAQ,cAAc;AAE5B,SAAO,wBAAwB,KAAK;AACtC;AAmBO,SAAS,uBAAkC;AAChD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW,CAAC,YAAyB,WAAW,OAAO;AAAA,IACvD,YAAY,CAAC,eAAkD;AAC7D,UAAI,CAAC,WAAW,OAAO;AACrB,eAAO,CAAC;AAAA,MACV;AAEA,aAAO;AAAA,QACL,OAAO,eAAe,WAAW,KAAK;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;;;ACvEA,SAAS,mBAAmB,SAAuC;AANnE;AAOE,QAAM,MAAM,QAAQ;AACpB,QAAM,QAAQ,QAAQ,QAAQ,OAAO;AAErC,MAAI,CAAC,OAAO,CAAC,OAAO;AAClB,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,MAAM,KAAK,IAAI,QAAQ,EAAE,QAAQ,OAAO;AAC1D,QAAM,SAAQ,WAAM,iBAAiB,gBAAgB,EAAE,SAAS,MAAlD,mBAAqD,aAAa;AAEhF,SAAO,QAAQ,CAAC,SAAS,OAAO,EAAE,CAAC,IAAI;AACzC;AAUO,SAAS,cAAc,SAAuC;AACnE,QAAM,WAAW,QAAQ,aAAa,UAAU;AAEhD,MAAI,UAAU;AACZ,WAAO,SAAS,MAAM,GAAG,EAAE,IAAI,WAAS,SAAS,OAAO,EAAE,CAAC;AAAA,EAC7D;AAEA,SAAO,mBAAmB,OAAO;AACnC;;;
|
|
1
|
+
{"version":3,"sources":["../../src/cell/index.ts","../../src/cell/table-cell.ts","../../src/utils/parseAlign.ts","../../src/utils/parseColwidth.ts","../../src/utils/fillEmptyCellContent.ts"],"sourcesContent":["export * from './table-cell.js'\n","import '../types.js'\n\nimport { mergeAttributes, Node } from '@tiptap/core'\n\nimport { createAlignAttribute } from '../utils/parseAlign.js'\nimport { parseColwidth } from '../utils/parseColwidth.js'\nimport { fillEmptyCellContent, isEmptyCellElement } from '../utils/fillEmptyCellContent.js'\n\nexport interface TableCellOptions {\n /**\n * The HTML attributes for a table cell node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\n/**\n * This extension allows you to create table cells.\n * @see https://www.tiptap.dev/api/nodes/table-cell\n */\nexport const TableCell = Node.create<TableCellOptions>({\n name: 'tableCell',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n content: 'block+',\n\n addAttributes() {\n return {\n colspan: {\n default: 1,\n },\n rowspan: {\n default: 1,\n },\n colwidth: {\n default: null,\n parseHTML: parseColwidth,\n },\n align: createAlignAttribute(),\n }\n },\n\n tableRole: 'cell',\n\n isolating: true,\n\n parseHTML() {\n return [\n {\n // Backfill empty cells; non-empty cells fall through to the rule below.\n tag: 'td',\n getAttrs: node => (isEmptyCellElement(node) ? {} : false),\n getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name]),\n },\n { tag: 'td' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['td', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n})\n","import type { Attribute } from '@tiptap/core'\n\n/**\n * Supported table cell alignment values\n */\nexport enum TableCellAlign {\n Left = 'left',\n Right = 'right',\n Center = 'center',\n}\n\n/**\n * Normalize unknown input into a supported table alignment\n *\n * @param value - A potential alignment value\n * @returns A valid TableCellAlign value or null\n */\nexport function normalizeTableCellAlign(value: unknown): TableCellAlign | null {\n if (\n value === TableCellAlign.Left ||\n value === TableCellAlign.Right ||\n value === TableCellAlign.Center\n ) {\n return value\n }\n\n return null\n}\n\n/**\n * Parse table cell alignment from an HTML element\n *\n * Prefers inline style (${\"`\"}text-align${\"`\"}) and falls back to the legacy\n * ${\"`\"}align${\"`\"} attribute.\n *\n * @param element - The table cell/header DOM element\n * @returns A valid TableCellAlign value or null\n */\nexport function parseAlign(element: HTMLElement): TableCellAlign | null {\n const styleAlign = (element.style.textAlign || '').trim().toLowerCase()\n const attrAlign = (element.getAttribute('align') || '').trim().toLowerCase()\n const align = styleAlign || attrAlign\n\n return normalizeTableCellAlign(align)\n}\n\n/**\n * Normalize alignment from a generic attrs object that may include an align field\n *\n * @param attributes - A node attrs-like object with an optional align field\n * @returns A valid TableCellAlign value or null.\n */\nexport function normalizeTableCellAlignFromAttributes(\n attributes: { align?: TableCellAlign } | null | undefined,\n): TableCellAlign | null {\n return normalizeTableCellAlign(attributes?.align)\n}\n\n/**\n * Create a reusable Tiptap attribute config for table alignment\n *\n * @returns A Tiptap Attribute definition that parses and renders table alignment\n */\nexport function createAlignAttribute(): Attribute {\n return {\n default: null,\n parseHTML: (element: HTMLElement) => parseAlign(element),\n renderHTML: (attributes: { align?: TableCellAlign | null }) => {\n if (!attributes.align) {\n return {}\n }\n\n return {\n style: `text-align: ${attributes.align}`,\n }\n },\n }\n}\n","/**\n * reads the width of the `<col>` element matching a cell's column from the table's `<colgroup>`\n *\n * @param element - The table cell/header DOM element\n * @returns - An array with the column width in pixels or null\n */\nfunction parseColgroupWidth(element: HTMLElement): number[] | null {\n const row = element.parentElement\n const table = element.closest('table')\n\n if (!row || !table) {\n return null\n }\n\n const cellIndex = Array.from(row.children).indexOf(element)\n const width = table.querySelectorAll('colgroup > col')[cellIndex]?.getAttribute('width')\n\n return width ? [parseInt(width, 10)] : null\n}\n\n/**\n * Parse the column width/s of a table cell/header from an HTML element.\n * it prefers the `colwidth` attribute and if not-provided falls back to the `width` attribute\n * of the matching `<col>` element in the table's `<colgroup>`.\n *\n * @param element - The table cell/header DOM element\n * @returns - An array of column widths in pixels or null\n */\nexport function parseColwidth(element: HTMLElement): number[] | null {\n const colwidth = element.getAttribute('colwidth')\n\n if (colwidth) {\n return colwidth.split(',').map(width => parseInt(width, 10))\n }\n\n return parseColgroupWidth(element)\n}\n","import type { NodeType } from '@tiptap/pm/model'\nimport { Fragment } from '@tiptap/pm/model'\n\n// ASCII whitespace only, so a non-breaking-space cell is not treated as empty.\nconst COLLAPSIBLE_WHITESPACE = /[ \\t\\r\\n\\f]+/g\n\n/** Whether a cell/header element has no child elements and only collapsible whitespace. */\nexport function isEmptyCellElement(element: HTMLElement): boolean {\n if (element.children.length > 0) {\n return false\n }\n\n return (element.textContent ?? '').replace(COLLAPSIBLE_WHITESPACE, '') === ''\n}\n\n/** Builds a cell/header's minimal `block+` content, derived from the node type. */\nexport function fillEmptyCellContent(cellType: NodeType): Fragment {\n const filled = cellType.createAndFill()\n\n if (!filled) {\n throw new Error(`[tiptap error]: \"${cellType.name}\" has no default content to backfill.`)\n }\n\n return filled.content\n}\n"],"mappings":"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","names":[]}
|
package/dist/cell/index.js
CHANGED
|
@@ -49,6 +49,23 @@ function parseColwidth(element) {
|
|
|
49
49
|
return parseColgroupWidth(element);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// src/utils/fillEmptyCellContent.ts
|
|
53
|
+
var COLLAPSIBLE_WHITESPACE = /[ \t\r\n\f]+/g;
|
|
54
|
+
function isEmptyCellElement(element) {
|
|
55
|
+
var _a;
|
|
56
|
+
if (element.children.length > 0) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return ((_a = element.textContent) != null ? _a : "").replace(COLLAPSIBLE_WHITESPACE, "") === "";
|
|
60
|
+
}
|
|
61
|
+
function fillEmptyCellContent(cellType) {
|
|
62
|
+
const filled = cellType.createAndFill();
|
|
63
|
+
if (!filled) {
|
|
64
|
+
throw new Error(`[tiptap error]: "${cellType.name}" has no default content to backfill.`);
|
|
65
|
+
}
|
|
66
|
+
return filled.content;
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
// src/cell/table-cell.ts
|
|
53
70
|
var TableCell = Node.create({
|
|
54
71
|
name: "tableCell",
|
|
@@ -76,7 +93,15 @@ var TableCell = Node.create({
|
|
|
76
93
|
tableRole: "cell",
|
|
77
94
|
isolating: true,
|
|
78
95
|
parseHTML() {
|
|
79
|
-
return [
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
// Backfill empty cells; non-empty cells fall through to the rule below.
|
|
99
|
+
tag: "td",
|
|
100
|
+
getAttrs: (node) => isEmptyCellElement(node) ? {} : false,
|
|
101
|
+
getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name])
|
|
102
|
+
},
|
|
103
|
+
{ tag: "td" }
|
|
104
|
+
];
|
|
80
105
|
},
|
|
81
106
|
renderHTML({ HTMLAttributes }) {
|
|
82
107
|
return ["td", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
|
package/dist/cell/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cell/table-cell.ts","../../src/utils/parseAlign.ts","../../src/utils/parseColwidth.ts"],"sourcesContent":["import '../types.js'\n\nimport { mergeAttributes, Node } from '@tiptap/core'\n\nimport { createAlignAttribute } from '../utils/parseAlign.js'\nimport { parseColwidth } from '../utils/parseColwidth.js'\n\nexport interface TableCellOptions {\n /**\n * The HTML attributes for a table cell node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\n/**\n * This extension allows you to create table cells.\n * @see https://www.tiptap.dev/api/nodes/table-cell\n */\nexport const TableCell = Node.create<TableCellOptions>({\n name: 'tableCell',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n content: 'block+',\n\n addAttributes() {\n return {\n colspan: {\n default: 1,\n },\n rowspan: {\n default: 1,\n },\n colwidth: {\n default: null,\n parseHTML: parseColwidth,\n },\n align: createAlignAttribute(),\n }\n },\n\n tableRole: 'cell',\n\n isolating: true,\n\n parseHTML() {\n return [{ tag: 'td' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['td', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n})\n","import type { Attribute } from '@tiptap/core'\n\n/**\n * Supported table cell alignment values\n */\nexport enum TableCellAlign {\n Left = 'left',\n Right = 'right',\n Center = 'center',\n}\n\n/**\n * Normalize unknown input into a supported table alignment\n *\n * @param value - A potential alignment value\n * @returns A valid TableCellAlign value or null\n */\nexport function normalizeTableCellAlign(value: unknown): TableCellAlign | null {\n if (\n value === TableCellAlign.Left ||\n value === TableCellAlign.Right ||\n value === TableCellAlign.Center\n ) {\n return value\n }\n\n return null\n}\n\n/**\n * Parse table cell alignment from an HTML element\n *\n * Prefers inline style (${\"`\"}text-align${\"`\"}) and falls back to the legacy\n * ${\"`\"}align${\"`\"} attribute.\n *\n * @param element - The table cell/header DOM element\n * @returns A valid TableCellAlign value or null\n */\nexport function parseAlign(element: HTMLElement): TableCellAlign | null {\n const styleAlign = (element.style.textAlign || '').trim().toLowerCase()\n const attrAlign = (element.getAttribute('align') || '').trim().toLowerCase()\n const align = styleAlign || attrAlign\n\n return normalizeTableCellAlign(align)\n}\n\n/**\n * Normalize alignment from a generic attrs object that may include an align field\n *\n * @param attributes - A node attrs-like object with an optional align field\n * @returns A valid TableCellAlign value or null.\n */\nexport function normalizeTableCellAlignFromAttributes(\n attributes: { align?: TableCellAlign } | null | undefined,\n): TableCellAlign | null {\n return normalizeTableCellAlign(attributes?.align)\n}\n\n/**\n * Create a reusable Tiptap attribute config for table alignment\n *\n * @returns A Tiptap Attribute definition that parses and renders table alignment\n */\nexport function createAlignAttribute(): Attribute {\n return {\n default: null,\n parseHTML: (element: HTMLElement) => parseAlign(element),\n renderHTML: (attributes: { align?: TableCellAlign | null }) => {\n if (!attributes.align) {\n return {}\n }\n\n return {\n style: `text-align: ${attributes.align}`,\n }\n },\n }\n}\n","/**\n * reads the width of the `<col>` element matching a cell's column from the table's `<colgroup>`\n *\n * @param element - The table cell/header DOM element\n * @returns - An array with the column width in pixels or null\n */\nfunction parseColgroupWidth(element: HTMLElement): number[] | null {\n const row = element.parentElement\n const table = element.closest('table')\n\n if (!row || !table) {\n return null\n }\n\n const cellIndex = Array.from(row.children).indexOf(element)\n const width = table.querySelectorAll('colgroup > col')[cellIndex]?.getAttribute('width')\n\n return width ? [parseInt(width, 10)] : null\n}\n\n/**\n * Parse the column width/s of a table cell/header from an HTML element.\n * it prefers the `colwidth` attribute and if not-provided falls back to the `width` attribute\n * of the matching `<col>` element in the table's `<colgroup>`.\n *\n * @param element - The table cell/header DOM element\n * @returns - An array of column widths in pixels or null\n */\nexport function parseColwidth(element: HTMLElement): number[] | null {\n const colwidth = element.getAttribute('colwidth')\n\n if (colwidth) {\n return colwidth.split(',').map(width => parseInt(width, 10))\n }\n\n return parseColgroupWidth(element)\n}\n"],"mappings":";AAEA,SAAS,iBAAiB,YAAY;;;ACe/B,SAAS,wBAAwB,OAAuC;AAC7E,MACE,UAAU,qBACV,UAAU,uBACV,UAAU,uBACV;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAWO,SAAS,WAAW,SAA6C;AACtE,QAAM,cAAc,QAAQ,MAAM,aAAa,IAAI,KAAK,EAAE,YAAY;AACtE,QAAM,aAAa,QAAQ,aAAa,OAAO,KAAK,IAAI,KAAK,EAAE,YAAY;AAC3E,QAAM,QAAQ,cAAc;AAE5B,SAAO,wBAAwB,KAAK;AACtC;AAmBO,SAAS,uBAAkC;AAChD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW,CAAC,YAAyB,WAAW,OAAO;AAAA,IACvD,YAAY,CAAC,eAAkD;AAC7D,UAAI,CAAC,WAAW,OAAO;AACrB,eAAO,CAAC;AAAA,MACV;AAEA,aAAO;AAAA,QACL,OAAO,eAAe,WAAW,KAAK;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;;;ACvEA,SAAS,mBAAmB,SAAuC;AANnE;AAOE,QAAM,MAAM,QAAQ;AACpB,QAAM,QAAQ,QAAQ,QAAQ,OAAO;AAErC,MAAI,CAAC,OAAO,CAAC,OAAO;AAClB,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,MAAM,KAAK,IAAI,QAAQ,EAAE,QAAQ,OAAO;AAC1D,QAAM,SAAQ,WAAM,iBAAiB,gBAAgB,EAAE,SAAS,MAAlD,mBAAqD,aAAa;AAEhF,SAAO,QAAQ,CAAC,SAAS,OAAO,EAAE,CAAC,IAAI;AACzC;AAUO,SAAS,cAAc,SAAuC;AACnE,QAAM,WAAW,QAAQ,aAAa,UAAU;AAEhD,MAAI,UAAU;AACZ,WAAO,SAAS,MAAM,GAAG,EAAE,IAAI,WAAS,SAAS,OAAO,EAAE,CAAC;AAAA,EAC7D;AAEA,SAAO,mBAAmB,OAAO;AACnC;;;
|
|
1
|
+
{"version":3,"sources":["../../src/cell/table-cell.ts","../../src/utils/parseAlign.ts","../../src/utils/parseColwidth.ts","../../src/utils/fillEmptyCellContent.ts"],"sourcesContent":["import '../types.js'\n\nimport { mergeAttributes, Node } from '@tiptap/core'\n\nimport { createAlignAttribute } from '../utils/parseAlign.js'\nimport { parseColwidth } from '../utils/parseColwidth.js'\nimport { fillEmptyCellContent, isEmptyCellElement } from '../utils/fillEmptyCellContent.js'\n\nexport interface TableCellOptions {\n /**\n * The HTML attributes for a table cell node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\n/**\n * This extension allows you to create table cells.\n * @see https://www.tiptap.dev/api/nodes/table-cell\n */\nexport const TableCell = Node.create<TableCellOptions>({\n name: 'tableCell',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n content: 'block+',\n\n addAttributes() {\n return {\n colspan: {\n default: 1,\n },\n rowspan: {\n default: 1,\n },\n colwidth: {\n default: null,\n parseHTML: parseColwidth,\n },\n align: createAlignAttribute(),\n }\n },\n\n tableRole: 'cell',\n\n isolating: true,\n\n parseHTML() {\n return [\n {\n // Backfill empty cells; non-empty cells fall through to the rule below.\n tag: 'td',\n getAttrs: node => (isEmptyCellElement(node) ? {} : false),\n getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name]),\n },\n { tag: 'td' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['td', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n})\n","import type { Attribute } from '@tiptap/core'\n\n/**\n * Supported table cell alignment values\n */\nexport enum TableCellAlign {\n Left = 'left',\n Right = 'right',\n Center = 'center',\n}\n\n/**\n * Normalize unknown input into a supported table alignment\n *\n * @param value - A potential alignment value\n * @returns A valid TableCellAlign value or null\n */\nexport function normalizeTableCellAlign(value: unknown): TableCellAlign | null {\n if (\n value === TableCellAlign.Left ||\n value === TableCellAlign.Right ||\n value === TableCellAlign.Center\n ) {\n return value\n }\n\n return null\n}\n\n/**\n * Parse table cell alignment from an HTML element\n *\n * Prefers inline style (${\"`\"}text-align${\"`\"}) and falls back to the legacy\n * ${\"`\"}align${\"`\"} attribute.\n *\n * @param element - The table cell/header DOM element\n * @returns A valid TableCellAlign value or null\n */\nexport function parseAlign(element: HTMLElement): TableCellAlign | null {\n const styleAlign = (element.style.textAlign || '').trim().toLowerCase()\n const attrAlign = (element.getAttribute('align') || '').trim().toLowerCase()\n const align = styleAlign || attrAlign\n\n return normalizeTableCellAlign(align)\n}\n\n/**\n * Normalize alignment from a generic attrs object that may include an align field\n *\n * @param attributes - A node attrs-like object with an optional align field\n * @returns A valid TableCellAlign value or null.\n */\nexport function normalizeTableCellAlignFromAttributes(\n attributes: { align?: TableCellAlign } | null | undefined,\n): TableCellAlign | null {\n return normalizeTableCellAlign(attributes?.align)\n}\n\n/**\n * Create a reusable Tiptap attribute config for table alignment\n *\n * @returns A Tiptap Attribute definition that parses and renders table alignment\n */\nexport function createAlignAttribute(): Attribute {\n return {\n default: null,\n parseHTML: (element: HTMLElement) => parseAlign(element),\n renderHTML: (attributes: { align?: TableCellAlign | null }) => {\n if (!attributes.align) {\n return {}\n }\n\n return {\n style: `text-align: ${attributes.align}`,\n }\n },\n }\n}\n","/**\n * reads the width of the `<col>` element matching a cell's column from the table's `<colgroup>`\n *\n * @param element - The table cell/header DOM element\n * @returns - An array with the column width in pixels or null\n */\nfunction parseColgroupWidth(element: HTMLElement): number[] | null {\n const row = element.parentElement\n const table = element.closest('table')\n\n if (!row || !table) {\n return null\n }\n\n const cellIndex = Array.from(row.children).indexOf(element)\n const width = table.querySelectorAll('colgroup > col')[cellIndex]?.getAttribute('width')\n\n return width ? [parseInt(width, 10)] : null\n}\n\n/**\n * Parse the column width/s of a table cell/header from an HTML element.\n * it prefers the `colwidth` attribute and if not-provided falls back to the `width` attribute\n * of the matching `<col>` element in the table's `<colgroup>`.\n *\n * @param element - The table cell/header DOM element\n * @returns - An array of column widths in pixels or null\n */\nexport function parseColwidth(element: HTMLElement): number[] | null {\n const colwidth = element.getAttribute('colwidth')\n\n if (colwidth) {\n return colwidth.split(',').map(width => parseInt(width, 10))\n }\n\n return parseColgroupWidth(element)\n}\n","import type { NodeType } from '@tiptap/pm/model'\nimport { Fragment } from '@tiptap/pm/model'\n\n// ASCII whitespace only, so a non-breaking-space cell is not treated as empty.\nconst COLLAPSIBLE_WHITESPACE = /[ \\t\\r\\n\\f]+/g\n\n/** Whether a cell/header element has no child elements and only collapsible whitespace. */\nexport function isEmptyCellElement(element: HTMLElement): boolean {\n if (element.children.length > 0) {\n return false\n }\n\n return (element.textContent ?? '').replace(COLLAPSIBLE_WHITESPACE, '') === ''\n}\n\n/** Builds a cell/header's minimal `block+` content, derived from the node type. */\nexport function fillEmptyCellContent(cellType: NodeType): Fragment {\n const filled = cellType.createAndFill()\n\n if (!filled) {\n throw new Error(`[tiptap error]: \"${cellType.name}\" has no default content to backfill.`)\n }\n\n return filled.content\n}\n"],"mappings":"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","names":[]}
|
package/dist/header/index.cjs
CHANGED
|
@@ -75,6 +75,23 @@ function parseColwidth(element) {
|
|
|
75
75
|
return parseColgroupWidth(element);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// src/utils/fillEmptyCellContent.ts
|
|
79
|
+
var COLLAPSIBLE_WHITESPACE = /[ \t\r\n\f]+/g;
|
|
80
|
+
function isEmptyCellElement(element) {
|
|
81
|
+
var _a;
|
|
82
|
+
if (element.children.length > 0) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return ((_a = element.textContent) != null ? _a : "").replace(COLLAPSIBLE_WHITESPACE, "") === "";
|
|
86
|
+
}
|
|
87
|
+
function fillEmptyCellContent(cellType) {
|
|
88
|
+
const filled = cellType.createAndFill();
|
|
89
|
+
if (!filled) {
|
|
90
|
+
throw new Error(`[tiptap error]: "${cellType.name}" has no default content to backfill.`);
|
|
91
|
+
}
|
|
92
|
+
return filled.content;
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
// src/header/table-header.ts
|
|
79
96
|
var TableHeader = import_core.Node.create({
|
|
80
97
|
name: "tableHeader",
|
|
@@ -102,7 +119,15 @@ var TableHeader = import_core.Node.create({
|
|
|
102
119
|
tableRole: "header_cell",
|
|
103
120
|
isolating: true,
|
|
104
121
|
parseHTML() {
|
|
105
|
-
return [
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
// Backfill empty cells; non-empty cells fall through to the rule below.
|
|
125
|
+
tag: "th",
|
|
126
|
+
getAttrs: (node) => isEmptyCellElement(node) ? {} : false,
|
|
127
|
+
getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name])
|
|
128
|
+
},
|
|
129
|
+
{ tag: "th" }
|
|
130
|
+
];
|
|
106
131
|
},
|
|
107
132
|
renderHTML({ HTMLAttributes }) {
|
|
108
133
|
return ["th", (0, import_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes), 0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/header/index.ts","../../src/header/table-header.ts","../../src/utils/parseAlign.ts","../../src/utils/parseColwidth.ts"],"sourcesContent":["export * from './table-header.js'\n","import '../types.js'\n\nimport { mergeAttributes, Node } from '@tiptap/core'\n\nimport { createAlignAttribute } from '../utils/parseAlign.js'\nimport { parseColwidth } from '../utils/parseColwidth.js'\n\nexport interface TableHeaderOptions {\n /**\n * The HTML attributes for a table header node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\n/**\n * This extension allows you to create table headers.\n * @see https://www.tiptap.dev/api/nodes/table-header\n */\nexport const TableHeader = Node.create<TableHeaderOptions>({\n name: 'tableHeader',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n content: 'block+',\n\n addAttributes() {\n return {\n colspan: {\n default: 1,\n },\n rowspan: {\n default: 1,\n },\n colwidth: {\n default: null,\n parseHTML: parseColwidth,\n },\n align: createAlignAttribute(),\n }\n },\n\n tableRole: 'header_cell',\n\n isolating: true,\n\n parseHTML() {\n return [{ tag: 'th' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['th', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n})\n","import type { Attribute } from '@tiptap/core'\n\n/**\n * Supported table cell alignment values\n */\nexport enum TableCellAlign {\n Left = 'left',\n Right = 'right',\n Center = 'center',\n}\n\n/**\n * Normalize unknown input into a supported table alignment\n *\n * @param value - A potential alignment value\n * @returns A valid TableCellAlign value or null\n */\nexport function normalizeTableCellAlign(value: unknown): TableCellAlign | null {\n if (\n value === TableCellAlign.Left ||\n value === TableCellAlign.Right ||\n value === TableCellAlign.Center\n ) {\n return value\n }\n\n return null\n}\n\n/**\n * Parse table cell alignment from an HTML element\n *\n * Prefers inline style (${\"`\"}text-align${\"`\"}) and falls back to the legacy\n * ${\"`\"}align${\"`\"} attribute.\n *\n * @param element - The table cell/header DOM element\n * @returns A valid TableCellAlign value or null\n */\nexport function parseAlign(element: HTMLElement): TableCellAlign | null {\n const styleAlign = (element.style.textAlign || '').trim().toLowerCase()\n const attrAlign = (element.getAttribute('align') || '').trim().toLowerCase()\n const align = styleAlign || attrAlign\n\n return normalizeTableCellAlign(align)\n}\n\n/**\n * Normalize alignment from a generic attrs object that may include an align field\n *\n * @param attributes - A node attrs-like object with an optional align field\n * @returns A valid TableCellAlign value or null.\n */\nexport function normalizeTableCellAlignFromAttributes(\n attributes: { align?: TableCellAlign } | null | undefined,\n): TableCellAlign | null {\n return normalizeTableCellAlign(attributes?.align)\n}\n\n/**\n * Create a reusable Tiptap attribute config for table alignment\n *\n * @returns A Tiptap Attribute definition that parses and renders table alignment\n */\nexport function createAlignAttribute(): Attribute {\n return {\n default: null,\n parseHTML: (element: HTMLElement) => parseAlign(element),\n renderHTML: (attributes: { align?: TableCellAlign | null }) => {\n if (!attributes.align) {\n return {}\n }\n\n return {\n style: `text-align: ${attributes.align}`,\n }\n },\n }\n}\n","/**\n * reads the width of the `<col>` element matching a cell's column from the table's `<colgroup>`\n *\n * @param element - The table cell/header DOM element\n * @returns - An array with the column width in pixels or null\n */\nfunction parseColgroupWidth(element: HTMLElement): number[] | null {\n const row = element.parentElement\n const table = element.closest('table')\n\n if (!row || !table) {\n return null\n }\n\n const cellIndex = Array.from(row.children).indexOf(element)\n const width = table.querySelectorAll('colgroup > col')[cellIndex]?.getAttribute('width')\n\n return width ? [parseInt(width, 10)] : null\n}\n\n/**\n * Parse the column width/s of a table cell/header from an HTML element.\n * it prefers the `colwidth` attribute and if not-provided falls back to the `width` attribute\n * of the matching `<col>` element in the table's `<colgroup>`.\n *\n * @param element - The table cell/header DOM element\n * @returns - An array of column widths in pixels or null\n */\nexport function parseColwidth(element: HTMLElement): number[] | null {\n const colwidth = element.getAttribute('colwidth')\n\n if (colwidth) {\n return colwidth.split(',').map(width => parseInt(width, 10))\n }\n\n return parseColgroupWidth(element)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,kBAAsC;;;ACe/B,SAAS,wBAAwB,OAAuC;AAC7E,MACE,UAAU,qBACV,UAAU,uBACV,UAAU,uBACV;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAWO,SAAS,WAAW,SAA6C;AACtE,QAAM,cAAc,QAAQ,MAAM,aAAa,IAAI,KAAK,EAAE,YAAY;AACtE,QAAM,aAAa,QAAQ,aAAa,OAAO,KAAK,IAAI,KAAK,EAAE,YAAY;AAC3E,QAAM,QAAQ,cAAc;AAE5B,SAAO,wBAAwB,KAAK;AACtC;AAmBO,SAAS,uBAAkC;AAChD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW,CAAC,YAAyB,WAAW,OAAO;AAAA,IACvD,YAAY,CAAC,eAAkD;AAC7D,UAAI,CAAC,WAAW,OAAO;AACrB,eAAO,CAAC;AAAA,MACV;AAEA,aAAO;AAAA,QACL,OAAO,eAAe,WAAW,KAAK;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;;;ACvEA,SAAS,mBAAmB,SAAuC;AANnE;AAOE,QAAM,MAAM,QAAQ;AACpB,QAAM,QAAQ,QAAQ,QAAQ,OAAO;AAErC,MAAI,CAAC,OAAO,CAAC,OAAO;AAClB,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,MAAM,KAAK,IAAI,QAAQ,EAAE,QAAQ,OAAO;AAC1D,QAAM,SAAQ,WAAM,iBAAiB,gBAAgB,EAAE,SAAS,MAAlD,mBAAqD,aAAa;AAEhF,SAAO,QAAQ,CAAC,SAAS,OAAO,EAAE,CAAC,IAAI;AACzC;AAUO,SAAS,cAAc,SAAuC;AACnE,QAAM,WAAW,QAAQ,aAAa,UAAU;AAEhD,MAAI,UAAU;AACZ,WAAO,SAAS,MAAM,GAAG,EAAE,IAAI,WAAS,SAAS,OAAO,EAAE,CAAC;AAAA,EAC7D;AAEA,SAAO,mBAAmB,OAAO;AACnC;;;
|
|
1
|
+
{"version":3,"sources":["../../src/header/index.ts","../../src/header/table-header.ts","../../src/utils/parseAlign.ts","../../src/utils/parseColwidth.ts","../../src/utils/fillEmptyCellContent.ts"],"sourcesContent":["export * from './table-header.js'\n","import '../types.js'\n\nimport { mergeAttributes, Node } from '@tiptap/core'\n\nimport { createAlignAttribute } from '../utils/parseAlign.js'\nimport { parseColwidth } from '../utils/parseColwidth.js'\nimport { fillEmptyCellContent, isEmptyCellElement } from '../utils/fillEmptyCellContent.js'\n\nexport interface TableHeaderOptions {\n /**\n * The HTML attributes for a table header node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\n/**\n * This extension allows you to create table headers.\n * @see https://www.tiptap.dev/api/nodes/table-header\n */\nexport const TableHeader = Node.create<TableHeaderOptions>({\n name: 'tableHeader',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n content: 'block+',\n\n addAttributes() {\n return {\n colspan: {\n default: 1,\n },\n rowspan: {\n default: 1,\n },\n colwidth: {\n default: null,\n parseHTML: parseColwidth,\n },\n align: createAlignAttribute(),\n }\n },\n\n tableRole: 'header_cell',\n\n isolating: true,\n\n parseHTML() {\n return [\n {\n // Backfill empty cells; non-empty cells fall through to the rule below.\n tag: 'th',\n getAttrs: node => (isEmptyCellElement(node) ? {} : false),\n getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name]),\n },\n { tag: 'th' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['th', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n})\n","import type { Attribute } from '@tiptap/core'\n\n/**\n * Supported table cell alignment values\n */\nexport enum TableCellAlign {\n Left = 'left',\n Right = 'right',\n Center = 'center',\n}\n\n/**\n * Normalize unknown input into a supported table alignment\n *\n * @param value - A potential alignment value\n * @returns A valid TableCellAlign value or null\n */\nexport function normalizeTableCellAlign(value: unknown): TableCellAlign | null {\n if (\n value === TableCellAlign.Left ||\n value === TableCellAlign.Right ||\n value === TableCellAlign.Center\n ) {\n return value\n }\n\n return null\n}\n\n/**\n * Parse table cell alignment from an HTML element\n *\n * Prefers inline style (${\"`\"}text-align${\"`\"}) and falls back to the legacy\n * ${\"`\"}align${\"`\"} attribute.\n *\n * @param element - The table cell/header DOM element\n * @returns A valid TableCellAlign value or null\n */\nexport function parseAlign(element: HTMLElement): TableCellAlign | null {\n const styleAlign = (element.style.textAlign || '').trim().toLowerCase()\n const attrAlign = (element.getAttribute('align') || '').trim().toLowerCase()\n const align = styleAlign || attrAlign\n\n return normalizeTableCellAlign(align)\n}\n\n/**\n * Normalize alignment from a generic attrs object that may include an align field\n *\n * @param attributes - A node attrs-like object with an optional align field\n * @returns A valid TableCellAlign value or null.\n */\nexport function normalizeTableCellAlignFromAttributes(\n attributes: { align?: TableCellAlign } | null | undefined,\n): TableCellAlign | null {\n return normalizeTableCellAlign(attributes?.align)\n}\n\n/**\n * Create a reusable Tiptap attribute config for table alignment\n *\n * @returns A Tiptap Attribute definition that parses and renders table alignment\n */\nexport function createAlignAttribute(): Attribute {\n return {\n default: null,\n parseHTML: (element: HTMLElement) => parseAlign(element),\n renderHTML: (attributes: { align?: TableCellAlign | null }) => {\n if (!attributes.align) {\n return {}\n }\n\n return {\n style: `text-align: ${attributes.align}`,\n }\n },\n }\n}\n","/**\n * reads the width of the `<col>` element matching a cell's column from the table's `<colgroup>`\n *\n * @param element - The table cell/header DOM element\n * @returns - An array with the column width in pixels or null\n */\nfunction parseColgroupWidth(element: HTMLElement): number[] | null {\n const row = element.parentElement\n const table = element.closest('table')\n\n if (!row || !table) {\n return null\n }\n\n const cellIndex = Array.from(row.children).indexOf(element)\n const width = table.querySelectorAll('colgroup > col')[cellIndex]?.getAttribute('width')\n\n return width ? [parseInt(width, 10)] : null\n}\n\n/**\n * Parse the column width/s of a table cell/header from an HTML element.\n * it prefers the `colwidth` attribute and if not-provided falls back to the `width` attribute\n * of the matching `<col>` element in the table's `<colgroup>`.\n *\n * @param element - The table cell/header DOM element\n * @returns - An array of column widths in pixels or null\n */\nexport function parseColwidth(element: HTMLElement): number[] | null {\n const colwidth = element.getAttribute('colwidth')\n\n if (colwidth) {\n return colwidth.split(',').map(width => parseInt(width, 10))\n }\n\n return parseColgroupWidth(element)\n}\n","import type { NodeType } from '@tiptap/pm/model'\nimport { Fragment } from '@tiptap/pm/model'\n\n// ASCII whitespace only, so a non-breaking-space cell is not treated as empty.\nconst COLLAPSIBLE_WHITESPACE = /[ \\t\\r\\n\\f]+/g\n\n/** Whether a cell/header element has no child elements and only collapsible whitespace. */\nexport function isEmptyCellElement(element: HTMLElement): boolean {\n if (element.children.length > 0) {\n return false\n }\n\n return (element.textContent ?? '').replace(COLLAPSIBLE_WHITESPACE, '') === ''\n}\n\n/** Builds a cell/header's minimal `block+` content, derived from the node type. */\nexport function fillEmptyCellContent(cellType: NodeType): Fragment {\n const filled = cellType.createAndFill()\n\n if (!filled) {\n throw new Error(`[tiptap error]: \"${cellType.name}\" has no default content to backfill.`)\n }\n\n return filled.content\n}\n"],"mappings":"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","names":[]}
|
package/dist/header/index.js
CHANGED
|
@@ -49,6 +49,23 @@ function parseColwidth(element) {
|
|
|
49
49
|
return parseColgroupWidth(element);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// src/utils/fillEmptyCellContent.ts
|
|
53
|
+
var COLLAPSIBLE_WHITESPACE = /[ \t\r\n\f]+/g;
|
|
54
|
+
function isEmptyCellElement(element) {
|
|
55
|
+
var _a;
|
|
56
|
+
if (element.children.length > 0) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return ((_a = element.textContent) != null ? _a : "").replace(COLLAPSIBLE_WHITESPACE, "") === "";
|
|
60
|
+
}
|
|
61
|
+
function fillEmptyCellContent(cellType) {
|
|
62
|
+
const filled = cellType.createAndFill();
|
|
63
|
+
if (!filled) {
|
|
64
|
+
throw new Error(`[tiptap error]: "${cellType.name}" has no default content to backfill.`);
|
|
65
|
+
}
|
|
66
|
+
return filled.content;
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
// src/header/table-header.ts
|
|
53
70
|
var TableHeader = Node.create({
|
|
54
71
|
name: "tableHeader",
|
|
@@ -76,7 +93,15 @@ var TableHeader = Node.create({
|
|
|
76
93
|
tableRole: "header_cell",
|
|
77
94
|
isolating: true,
|
|
78
95
|
parseHTML() {
|
|
79
|
-
return [
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
// Backfill empty cells; non-empty cells fall through to the rule below.
|
|
99
|
+
tag: "th",
|
|
100
|
+
getAttrs: (node) => isEmptyCellElement(node) ? {} : false,
|
|
101
|
+
getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name])
|
|
102
|
+
},
|
|
103
|
+
{ tag: "th" }
|
|
104
|
+
];
|
|
80
105
|
},
|
|
81
106
|
renderHTML({ HTMLAttributes }) {
|
|
82
107
|
return ["th", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
|
package/dist/header/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/header/table-header.ts","../../src/utils/parseAlign.ts","../../src/utils/parseColwidth.ts"],"sourcesContent":["import '../types.js'\n\nimport { mergeAttributes, Node } from '@tiptap/core'\n\nimport { createAlignAttribute } from '../utils/parseAlign.js'\nimport { parseColwidth } from '../utils/parseColwidth.js'\n\nexport interface TableHeaderOptions {\n /**\n * The HTML attributes for a table header node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\n/**\n * This extension allows you to create table headers.\n * @see https://www.tiptap.dev/api/nodes/table-header\n */\nexport const TableHeader = Node.create<TableHeaderOptions>({\n name: 'tableHeader',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n content: 'block+',\n\n addAttributes() {\n return {\n colspan: {\n default: 1,\n },\n rowspan: {\n default: 1,\n },\n colwidth: {\n default: null,\n parseHTML: parseColwidth,\n },\n align: createAlignAttribute(),\n }\n },\n\n tableRole: 'header_cell',\n\n isolating: true,\n\n parseHTML() {\n return [{ tag: 'th' }]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['th', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n})\n","import type { Attribute } from '@tiptap/core'\n\n/**\n * Supported table cell alignment values\n */\nexport enum TableCellAlign {\n Left = 'left',\n Right = 'right',\n Center = 'center',\n}\n\n/**\n * Normalize unknown input into a supported table alignment\n *\n * @param value - A potential alignment value\n * @returns A valid TableCellAlign value or null\n */\nexport function normalizeTableCellAlign(value: unknown): TableCellAlign | null {\n if (\n value === TableCellAlign.Left ||\n value === TableCellAlign.Right ||\n value === TableCellAlign.Center\n ) {\n return value\n }\n\n return null\n}\n\n/**\n * Parse table cell alignment from an HTML element\n *\n * Prefers inline style (${\"`\"}text-align${\"`\"}) and falls back to the legacy\n * ${\"`\"}align${\"`\"} attribute.\n *\n * @param element - The table cell/header DOM element\n * @returns A valid TableCellAlign value or null\n */\nexport function parseAlign(element: HTMLElement): TableCellAlign | null {\n const styleAlign = (element.style.textAlign || '').trim().toLowerCase()\n const attrAlign = (element.getAttribute('align') || '').trim().toLowerCase()\n const align = styleAlign || attrAlign\n\n return normalizeTableCellAlign(align)\n}\n\n/**\n * Normalize alignment from a generic attrs object that may include an align field\n *\n * @param attributes - A node attrs-like object with an optional align field\n * @returns A valid TableCellAlign value or null.\n */\nexport function normalizeTableCellAlignFromAttributes(\n attributes: { align?: TableCellAlign } | null | undefined,\n): TableCellAlign | null {\n return normalizeTableCellAlign(attributes?.align)\n}\n\n/**\n * Create a reusable Tiptap attribute config for table alignment\n *\n * @returns A Tiptap Attribute definition that parses and renders table alignment\n */\nexport function createAlignAttribute(): Attribute {\n return {\n default: null,\n parseHTML: (element: HTMLElement) => parseAlign(element),\n renderHTML: (attributes: { align?: TableCellAlign | null }) => {\n if (!attributes.align) {\n return {}\n }\n\n return {\n style: `text-align: ${attributes.align}`,\n }\n },\n }\n}\n","/**\n * reads the width of the `<col>` element matching a cell's column from the table's `<colgroup>`\n *\n * @param element - The table cell/header DOM element\n * @returns - An array with the column width in pixels or null\n */\nfunction parseColgroupWidth(element: HTMLElement): number[] | null {\n const row = element.parentElement\n const table = element.closest('table')\n\n if (!row || !table) {\n return null\n }\n\n const cellIndex = Array.from(row.children).indexOf(element)\n const width = table.querySelectorAll('colgroup > col')[cellIndex]?.getAttribute('width')\n\n return width ? [parseInt(width, 10)] : null\n}\n\n/**\n * Parse the column width/s of a table cell/header from an HTML element.\n * it prefers the `colwidth` attribute and if not-provided falls back to the `width` attribute\n * of the matching `<col>` element in the table's `<colgroup>`.\n *\n * @param element - The table cell/header DOM element\n * @returns - An array of column widths in pixels or null\n */\nexport function parseColwidth(element: HTMLElement): number[] | null {\n const colwidth = element.getAttribute('colwidth')\n\n if (colwidth) {\n return colwidth.split(',').map(width => parseInt(width, 10))\n }\n\n return parseColgroupWidth(element)\n}\n"],"mappings":";AAEA,SAAS,iBAAiB,YAAY;;;ACe/B,SAAS,wBAAwB,OAAuC;AAC7E,MACE,UAAU,qBACV,UAAU,uBACV,UAAU,uBACV;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAWO,SAAS,WAAW,SAA6C;AACtE,QAAM,cAAc,QAAQ,MAAM,aAAa,IAAI,KAAK,EAAE,YAAY;AACtE,QAAM,aAAa,QAAQ,aAAa,OAAO,KAAK,IAAI,KAAK,EAAE,YAAY;AAC3E,QAAM,QAAQ,cAAc;AAE5B,SAAO,wBAAwB,KAAK;AACtC;AAmBO,SAAS,uBAAkC;AAChD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW,CAAC,YAAyB,WAAW,OAAO;AAAA,IACvD,YAAY,CAAC,eAAkD;AAC7D,UAAI,CAAC,WAAW,OAAO;AACrB,eAAO,CAAC;AAAA,MACV;AAEA,aAAO;AAAA,QACL,OAAO,eAAe,WAAW,KAAK;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;;;ACvEA,SAAS,mBAAmB,SAAuC;AANnE;AAOE,QAAM,MAAM,QAAQ;AACpB,QAAM,QAAQ,QAAQ,QAAQ,OAAO;AAErC,MAAI,CAAC,OAAO,CAAC,OAAO;AAClB,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,MAAM,KAAK,IAAI,QAAQ,EAAE,QAAQ,OAAO;AAC1D,QAAM,SAAQ,WAAM,iBAAiB,gBAAgB,EAAE,SAAS,MAAlD,mBAAqD,aAAa;AAEhF,SAAO,QAAQ,CAAC,SAAS,OAAO,EAAE,CAAC,IAAI;AACzC;AAUO,SAAS,cAAc,SAAuC;AACnE,QAAM,WAAW,QAAQ,aAAa,UAAU;AAEhD,MAAI,UAAU;AACZ,WAAO,SAAS,MAAM,GAAG,EAAE,IAAI,WAAS,SAAS,OAAO,EAAE,CAAC;AAAA,EAC7D;AAEA,SAAO,mBAAmB,OAAO;AACnC;;;
|
|
1
|
+
{"version":3,"sources":["../../src/header/table-header.ts","../../src/utils/parseAlign.ts","../../src/utils/parseColwidth.ts","../../src/utils/fillEmptyCellContent.ts"],"sourcesContent":["import '../types.js'\n\nimport { mergeAttributes, Node } from '@tiptap/core'\n\nimport { createAlignAttribute } from '../utils/parseAlign.js'\nimport { parseColwidth } from '../utils/parseColwidth.js'\nimport { fillEmptyCellContent, isEmptyCellElement } from '../utils/fillEmptyCellContent.js'\n\nexport interface TableHeaderOptions {\n /**\n * The HTML attributes for a table header node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>\n}\n\n/**\n * This extension allows you to create table headers.\n * @see https://www.tiptap.dev/api/nodes/table-header\n */\nexport const TableHeader = Node.create<TableHeaderOptions>({\n name: 'tableHeader',\n\n addOptions() {\n return {\n HTMLAttributes: {},\n }\n },\n\n content: 'block+',\n\n addAttributes() {\n return {\n colspan: {\n default: 1,\n },\n rowspan: {\n default: 1,\n },\n colwidth: {\n default: null,\n parseHTML: parseColwidth,\n },\n align: createAlignAttribute(),\n }\n },\n\n tableRole: 'header_cell',\n\n isolating: true,\n\n parseHTML() {\n return [\n {\n // Backfill empty cells; non-empty cells fall through to the rule below.\n tag: 'th',\n getAttrs: node => (isEmptyCellElement(node) ? {} : false),\n getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name]),\n },\n { tag: 'th' },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return ['th', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]\n },\n})\n","import type { Attribute } from '@tiptap/core'\n\n/**\n * Supported table cell alignment values\n */\nexport enum TableCellAlign {\n Left = 'left',\n Right = 'right',\n Center = 'center',\n}\n\n/**\n * Normalize unknown input into a supported table alignment\n *\n * @param value - A potential alignment value\n * @returns A valid TableCellAlign value or null\n */\nexport function normalizeTableCellAlign(value: unknown): TableCellAlign | null {\n if (\n value === TableCellAlign.Left ||\n value === TableCellAlign.Right ||\n value === TableCellAlign.Center\n ) {\n return value\n }\n\n return null\n}\n\n/**\n * Parse table cell alignment from an HTML element\n *\n * Prefers inline style (${\"`\"}text-align${\"`\"}) and falls back to the legacy\n * ${\"`\"}align${\"`\"} attribute.\n *\n * @param element - The table cell/header DOM element\n * @returns A valid TableCellAlign value or null\n */\nexport function parseAlign(element: HTMLElement): TableCellAlign | null {\n const styleAlign = (element.style.textAlign || '').trim().toLowerCase()\n const attrAlign = (element.getAttribute('align') || '').trim().toLowerCase()\n const align = styleAlign || attrAlign\n\n return normalizeTableCellAlign(align)\n}\n\n/**\n * Normalize alignment from a generic attrs object that may include an align field\n *\n * @param attributes - A node attrs-like object with an optional align field\n * @returns A valid TableCellAlign value or null.\n */\nexport function normalizeTableCellAlignFromAttributes(\n attributes: { align?: TableCellAlign } | null | undefined,\n): TableCellAlign | null {\n return normalizeTableCellAlign(attributes?.align)\n}\n\n/**\n * Create a reusable Tiptap attribute config for table alignment\n *\n * @returns A Tiptap Attribute definition that parses and renders table alignment\n */\nexport function createAlignAttribute(): Attribute {\n return {\n default: null,\n parseHTML: (element: HTMLElement) => parseAlign(element),\n renderHTML: (attributes: { align?: TableCellAlign | null }) => {\n if (!attributes.align) {\n return {}\n }\n\n return {\n style: `text-align: ${attributes.align}`,\n }\n },\n }\n}\n","/**\n * reads the width of the `<col>` element matching a cell's column from the table's `<colgroup>`\n *\n * @param element - The table cell/header DOM element\n * @returns - An array with the column width in pixels or null\n */\nfunction parseColgroupWidth(element: HTMLElement): number[] | null {\n const row = element.parentElement\n const table = element.closest('table')\n\n if (!row || !table) {\n return null\n }\n\n const cellIndex = Array.from(row.children).indexOf(element)\n const width = table.querySelectorAll('colgroup > col')[cellIndex]?.getAttribute('width')\n\n return width ? [parseInt(width, 10)] : null\n}\n\n/**\n * Parse the column width/s of a table cell/header from an HTML element.\n * it prefers the `colwidth` attribute and if not-provided falls back to the `width` attribute\n * of the matching `<col>` element in the table's `<colgroup>`.\n *\n * @param element - The table cell/header DOM element\n * @returns - An array of column widths in pixels or null\n */\nexport function parseColwidth(element: HTMLElement): number[] | null {\n const colwidth = element.getAttribute('colwidth')\n\n if (colwidth) {\n return colwidth.split(',').map(width => parseInt(width, 10))\n }\n\n return parseColgroupWidth(element)\n}\n","import type { NodeType } from '@tiptap/pm/model'\nimport { Fragment } from '@tiptap/pm/model'\n\n// ASCII whitespace only, so a non-breaking-space cell is not treated as empty.\nconst COLLAPSIBLE_WHITESPACE = /[ \\t\\r\\n\\f]+/g\n\n/** Whether a cell/header element has no child elements and only collapsible whitespace. */\nexport function isEmptyCellElement(element: HTMLElement): boolean {\n if (element.children.length > 0) {\n return false\n }\n\n return (element.textContent ?? '').replace(COLLAPSIBLE_WHITESPACE, '') === ''\n}\n\n/** Builds a cell/header's minimal `block+` content, derived from the node type. */\nexport function fillEmptyCellContent(cellType: NodeType): Fragment {\n const filled = cellType.createAndFill()\n\n if (!filled) {\n throw new Error(`[tiptap error]: \"${cellType.name}\" has no default content to backfill.`)\n }\n\n return filled.content\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -90,6 +90,23 @@ function parseColwidth(element) {
|
|
|
90
90
|
return parseColgroupWidth(element);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// src/utils/fillEmptyCellContent.ts
|
|
94
|
+
var COLLAPSIBLE_WHITESPACE = /[ \t\r\n\f]+/g;
|
|
95
|
+
function isEmptyCellElement(element) {
|
|
96
|
+
var _a;
|
|
97
|
+
if (element.children.length > 0) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return ((_a = element.textContent) != null ? _a : "").replace(COLLAPSIBLE_WHITESPACE, "") === "";
|
|
101
|
+
}
|
|
102
|
+
function fillEmptyCellContent(cellType) {
|
|
103
|
+
const filled = cellType.createAndFill();
|
|
104
|
+
if (!filled) {
|
|
105
|
+
throw new Error(`[tiptap error]: "${cellType.name}" has no default content to backfill.`);
|
|
106
|
+
}
|
|
107
|
+
return filled.content;
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
// src/cell/table-cell.ts
|
|
94
111
|
var TableCell = import_core.Node.create({
|
|
95
112
|
name: "tableCell",
|
|
@@ -117,7 +134,15 @@ var TableCell = import_core.Node.create({
|
|
|
117
134
|
tableRole: "cell",
|
|
118
135
|
isolating: true,
|
|
119
136
|
parseHTML() {
|
|
120
|
-
return [
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
// Backfill empty cells; non-empty cells fall through to the rule below.
|
|
140
|
+
tag: "td",
|
|
141
|
+
getAttrs: (node) => isEmptyCellElement(node) ? {} : false,
|
|
142
|
+
getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name])
|
|
143
|
+
},
|
|
144
|
+
{ tag: "td" }
|
|
145
|
+
];
|
|
121
146
|
},
|
|
122
147
|
renderHTML({ HTMLAttributes }) {
|
|
123
148
|
return ["td", (0, import_core.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes), 0];
|
|
@@ -152,7 +177,15 @@ var TableHeader = import_core2.Node.create({
|
|
|
152
177
|
tableRole: "header_cell",
|
|
153
178
|
isolating: true,
|
|
154
179
|
parseHTML() {
|
|
155
|
-
return [
|
|
180
|
+
return [
|
|
181
|
+
{
|
|
182
|
+
// Backfill empty cells; non-empty cells fall through to the rule below.
|
|
183
|
+
tag: "th",
|
|
184
|
+
getAttrs: (node) => isEmptyCellElement(node) ? {} : false,
|
|
185
|
+
getContent: (_node, schema) => fillEmptyCellContent(schema.nodes[this.name])
|
|
186
|
+
},
|
|
187
|
+
{ tag: "th" }
|
|
188
|
+
];
|
|
156
189
|
},
|
|
157
190
|
renderHTML({ HTMLAttributes }) {
|
|
158
191
|
return ["th", (0, import_core2.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes), 0];
|
|
@@ -465,7 +498,9 @@ function renderTableToMarkdown(node, h, options = {}) {
|
|
|
465
498
|
} else {
|
|
466
499
|
raw = cellNode.content ? h.renderChildren(cellNode.content) : "";
|
|
467
500
|
}
|
|
468
|
-
const text = collapseWhitespace(
|
|
501
|
+
const text = collapseWhitespace(
|
|
502
|
+
raw.split(cellSep).join("\n").replace(/[ \t]*\r?\n[ \t]*/g, "<br>")
|
|
503
|
+
);
|
|
469
504
|
const isHeader = cellNode.type === "tableHeader";
|
|
470
505
|
const align = normalizeTableCellAlignFromAttributes(cellNode.attrs);
|
|
471
506
|
cells.push({ text, isHeader, align });
|