@tiptap/extension-table 3.28.0 → 3.29.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.
@@ -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 [{ tag: "td" }];
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];
@@ -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;;;AFhBO,IAAM,YAAY,iBAAK,OAAyB;AAAA,EACrD,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,SAAS;AAAA,EAET,gBAAgB;AACd,WAAO;AAAA,MACL,SAAS;AAAA,QACP,SAAS;AAAA,MACX;AAAA,MACA,SAAS;AAAA,QACP,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AAAA,MACA,OAAO,qBAAqB;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,WAAW;AAAA,EAEX,WAAW;AAAA,EAEX,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,KAAK,CAAC;AAAA,EACvB;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,WAAO,CAAC,UAAM,6BAAgB,KAAK,QAAQ,gBAAgB,cAAc,GAAG,CAAC;AAAA,EAC/E;AACF,CAAC;","names":[]}
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":[]}
@@ -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 [{ tag: "td" }];
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];
@@ -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;;;AFhBO,IAAM,YAAY,KAAK,OAAyB;AAAA,EACrD,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,SAAS;AAAA,EAET,gBAAgB;AACd,WAAO;AAAA,MACL,SAAS;AAAA,QACP,SAAS;AAAA,MACX;AAAA,MACA,SAAS;AAAA,QACP,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AAAA,MACA,OAAO,qBAAqB;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,WAAW;AAAA,EAEX,WAAW;AAAA,EAEX,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,KAAK,CAAC;AAAA,EACvB;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,WAAO,CAAC,MAAM,gBAAgB,KAAK,QAAQ,gBAAgB,cAAc,GAAG,CAAC;AAAA,EAC/E;AACF,CAAC;","names":[]}
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":[]}
@@ -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 [{ tag: "th" }];
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;;;AFhBO,IAAM,cAAc,iBAAK,OAA2B;AAAA,EACzD,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,SAAS;AAAA,EAET,gBAAgB;AACd,WAAO;AAAA,MACL,SAAS;AAAA,QACP,SAAS;AAAA,MACX;AAAA,MACA,SAAS;AAAA,QACP,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AAAA,MACA,OAAO,qBAAqB;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,WAAW;AAAA,EAEX,WAAW;AAAA,EAEX,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,KAAK,CAAC;AAAA,EACvB;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,WAAO,CAAC,UAAM,6BAAgB,KAAK,QAAQ,gBAAgB,cAAc,GAAG,CAAC;AAAA,EAC/E;AACF,CAAC;","names":[]}
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":[]}
@@ -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 [{ tag: "th" }];
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];
@@ -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;;;AFhBO,IAAM,cAAc,KAAK,OAA2B;AAAA,EACzD,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,SAAS;AAAA,EAET,gBAAgB;AACd,WAAO;AAAA,MACL,SAAS;AAAA,QACP,SAAS;AAAA,MACX;AAAA,MACA,SAAS;AAAA,QACP,SAAS;AAAA,MACX;AAAA,MACA,UAAU;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,MACb;AAAA,MACA,OAAO,qBAAqB;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,WAAW;AAAA,EAEX,WAAW;AAAA,EAEX,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,KAAK,CAAC;AAAA,EACvB;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,WAAO,CAAC,MAAM,gBAAgB,KAAK,QAAQ,gBAAgB,cAAc,GAAG,CAAC;AAAA,EAC/E;AACF,CAAC;","names":[]}
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 [{ tag: "td" }];
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 [{ tag: "th" }];
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(raw);
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 });