quill-table-up 3.3.2 → 3.5.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "quill-table-up",
3
3
  "type": "module",
4
- "version": "3.3.2",
4
+ "version": "3.5.0",
5
5
  "packageManager": "pnpm@10.28.2",
6
6
  "description": "A table module for quill2.x",
7
7
  "author": "zzxming",
@@ -2,6 +2,7 @@ import Quill from 'quill';
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { TableCellInnerFormat } from '../../formats';
4
4
  import { TableUp } from '../../table-up';
5
+ import { parseCSSRules } from '../../utils';
5
6
  import { createQuillWithTableModule, createTableBodyHTML, createTableCaptionHTML, createTableDeltaOps, createTableHTML, createTaleColHTML, datasetTag, expectDelta, replaceAttrEmptyRow, simulatePasteHTML } from './utils';
6
7
 
7
8
  const Delta = Quill.import('delta');
@@ -2174,3 +2175,230 @@ describe('test TableUp `getHTMLByCell`', () => {
2174
2175
  }
2175
2176
  });
2176
2177
  });
2178
+
2179
+ describe('clipboard style block resolution', () => {
2180
+ it('class-based background-color is preserved on table cell', () => {
2181
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2182
+ const delta = quill.clipboard.convert({
2183
+ html: '<style>.xl65{background-color:#FFC000}</style><table><tr><td class="xl65">text</td></tr></table>',
2184
+ });
2185
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2186
+ expect(cellOp).toBeDefined();
2187
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2188
+ expect(style).toContain('background-color');
2189
+ expect(style).toMatch(/#FFC000|rgb\(255, 192, 0\)/i);
2190
+ });
2191
+
2192
+ it('class-based color is preserved as Quill inline format', () => {
2193
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2194
+ const delta = quill.clipboard.convert({
2195
+ html: '<style>.xl65{color:red}</style><table><tr><td class="xl65">text</td></tr></table>',
2196
+ });
2197
+ // text and \n may be merged into a single op when attributes match
2198
+ const textOp = delta.ops.find(op => typeof op.insert === 'string' && (op.insert as string).includes('text'));
2199
+ expect(textOp).toBeDefined();
2200
+ expect(textOp!.attributes?.color).toBeTruthy();
2201
+ });
2202
+
2203
+ it('class-based bold is preserved as Quill inline format', () => {
2204
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2205
+ const delta = quill.clipboard.convert({
2206
+ html: '<style>.xl65{font-weight:bold}</style><table><tr><td class="xl65">text</td></tr></table>',
2207
+ });
2208
+ // text and \n may be merged into a single op when attributes match
2209
+ const textOp = delta.ops.find(op => typeof op.insert === 'string' && (op.insert as string).includes('text'));
2210
+ expect(textOp).toBeDefined();
2211
+ expect(textOp!.attributes?.bold).toBe(true);
2212
+ });
2213
+
2214
+ it('inline style takes priority over class style', () => {
2215
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2216
+ const delta = quill.clipboard.convert({
2217
+ html: '<style>.xl65{background-color:red}</style><table><tr><td class="xl65" style="background-color:blue">text</td></tr></table>',
2218
+ });
2219
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2220
+ expect(cellOp).toBeDefined();
2221
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2222
+ expect(style).toContain('background-color');
2223
+ expect(style).toMatch(/blue/i);
2224
+ expect(style).not.toMatch(/red/i);
2225
+ });
2226
+
2227
+ it('@-rules are skipped without error', () => {
2228
+ const rules = parseCSSRules('@page{margin:1in} .xl65{background:red}');
2229
+ expect(rules.length).toBe(1);
2230
+ expect(rules[0].selector).toBe('.xl65');
2231
+ expect(rules[0].styles.background).toBe('red');
2232
+ });
2233
+
2234
+ it('multiple style blocks are all processed', () => {
2235
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2236
+ const delta = quill.clipboard.convert({
2237
+ html: '<style>.a{background-color:red}</style><style>.b{background-color:blue}</style><table><tr><td class="a">A</td><td class="b">B</td></tr></table>',
2238
+ });
2239
+ const cellOps = delta.ops.filter(op => op.attributes?.['table-up-cell-inner']);
2240
+ expect(cellOps.length).toBeGreaterThanOrEqual(2);
2241
+ expect((cellOps[0].attributes!['table-up-cell-inner'] as any).style).toContain('background-color');
2242
+ expect((cellOps[1].attributes!['table-up-cell-inner'] as any).style).toContain('background-color');
2243
+ });
2244
+
2245
+ it('simple tag selectors (td) are skipped by default', () => {
2246
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2247
+ const delta = quill.clipboard.convert({
2248
+ html: '<style>td{background-color:red} .xl65{background-color:blue}</style><table><tr><td class="xl65">text</td></tr></table>',
2249
+ });
2250
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2251
+ expect(cellOp).toBeDefined();
2252
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2253
+ // class selector applied, tag selector skipped
2254
+ expect(style).toMatch(/blue/i);
2255
+ });
2256
+
2257
+ it('descendant tag selectors (table td) are skipped by default', () => {
2258
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2259
+ const delta = quill.clipboard.convert({
2260
+ html: '<style>table td{background-color:red} .xl65{background-color:blue}</style><table><tr><td class="xl65">text</td></tr></table>',
2261
+ });
2262
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2263
+ expect(cellOp).toBeDefined();
2264
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2265
+ expect(style).toMatch(/blue/i);
2266
+ expect(style).not.toMatch(/red/i);
2267
+ });
2268
+
2269
+ it('child combinator tag selectors (tr > td) are skipped by default', () => {
2270
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2271
+ const delta = quill.clipboard.convert({
2272
+ html: '<style>tr > td{background-color:red}</style><table><tr><td>text</td></tr></table>',
2273
+ });
2274
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2275
+ expect(cellOp).toBeDefined();
2276
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style || '';
2277
+ expect(style).not.toContain('background-color');
2278
+ });
2279
+
2280
+ it('deep combinator tag selectors (table > tbody > tr > td) are skipped by default', () => {
2281
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2282
+ const delta = quill.clipboard.convert({
2283
+ html: '<style>table > tbody > tr > td{background-color:red}</style><table><tbody><tr><td>text</td></tr></tbody></table>',
2284
+ });
2285
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2286
+ expect(cellOp).toBeDefined();
2287
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style || '';
2288
+ expect(style).not.toContain('background-color');
2289
+ });
2290
+
2291
+ it('sibling combinator tag selectors (td + td) are skipped by default', () => {
2292
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2293
+ const delta = quill.clipboard.convert({
2294
+ html: '<style>td + td{background-color:red}</style><table><tr><td>a</td><td>b</td></tr></table>',
2295
+ });
2296
+ const cellOps = delta.ops.filter(op => op.attributes?.['table-up-cell-inner']);
2297
+ for (const op of cellOps) {
2298
+ const style = (op.attributes!['table-up-cell-inner'] as any).style || '';
2299
+ expect(style).not.toContain('background-color');
2300
+ }
2301
+ });
2302
+
2303
+ it('tag+class selectors (td.xl65) are NOT skipped', () => {
2304
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2305
+ const delta = quill.clipboard.convert({
2306
+ html: '<style>td.xl65{background-color:red}</style><table><tr><td class="xl65">text</td></tr></table>',
2307
+ });
2308
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2309
+ expect(cellOp).toBeDefined();
2310
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2311
+ expect(style).toContain('background-color');
2312
+ });
2313
+
2314
+ it('tag+id selectors (td#main) are NOT skipped', () => {
2315
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2316
+ const delta = quill.clipboard.convert({
2317
+ html: '<style>td#main{background-color:red}</style><table><tr><td id="main">text</td></tr></table>',
2318
+ });
2319
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2320
+ expect(cellOp).toBeDefined();
2321
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2322
+ expect(style).toContain('background-color');
2323
+ });
2324
+
2325
+ it('tag+attribute selectors (td[data-x]) are NOT skipped', () => {
2326
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2327
+ const delta = quill.clipboard.convert({
2328
+ html: '<style>td[data-x]{background-color:red}</style><table><tr><td data-x="1">text</td></tr></table>',
2329
+ });
2330
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2331
+ expect(cellOp).toBeDefined();
2332
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2333
+ expect(style).toContain('background-color');
2334
+ });
2335
+
2336
+ it('class descendant tag selectors (.cls td) are NOT skipped', () => {
2337
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2338
+ const delta = quill.clipboard.convert({
2339
+ html: '<style>.wrap td{background-color:#FFC000}</style><table class="wrap"><tr><td>text</td></tr></table>',
2340
+ });
2341
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2342
+ expect(cellOp).toBeDefined();
2343
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2344
+ expect(style).toContain('background-color');
2345
+ expect(style).toMatch(/#FFC000|rgb\(255, 192, 0\)/i);
2346
+ });
2347
+
2348
+ it('pseudo selectors (td:first-child) are NOT skipped', () => {
2349
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2350
+ const delta = quill.clipboard.convert({
2351
+ html: '<style>td:first-child{background-color:red}</style><table><tr><td>a</td><td>b</td></tr></table>',
2352
+ });
2353
+ const cellOps = delta.ops.filter(op => op.attributes?.['table-up-cell-inner']);
2354
+ // first cell should have background-color, second should not
2355
+ const styles = cellOps.map(op => (op.attributes!['table-up-cell-inner'] as any).style || '');
2356
+ expect(styles.some(s => s.includes('background-color'))).toBe(true);
2357
+ expect(styles.some(s => !s.includes('background-color'))).toBe(true);
2358
+ });
2359
+
2360
+ it('comma-separated mixed selectors skip only the tag-only parts', () => {
2361
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true });
2362
+ const delta = quill.clipboard.convert({
2363
+ html: '<style>td, .xl65{background-color:red}</style><table><tr><td class="xl65">text</td></tr></table>',
2364
+ });
2365
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2366
+ expect(cellOp).toBeDefined();
2367
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2368
+ // ".xl65" part applied (tag "td" part skipped, but .xl65 matches the same element)
2369
+ expect(style).toContain('background-color');
2370
+ });
2371
+
2372
+ it('complex tag selectors are applied when pasteDefaultTagStyle is true', () => {
2373
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true, pasteDefaultTagStyle: true });
2374
+ const delta = quill.clipboard.convert({
2375
+ html: '<style>table td{background-color:red}</style><table><tr><td>text</td></tr></table>',
2376
+ });
2377
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2378
+ expect(cellOp).toBeDefined();
2379
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2380
+ expect(style).toContain('background-color');
2381
+ });
2382
+
2383
+ it('pasteStyleSheet=false disables style resolution', () => {
2384
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: false });
2385
+ const delta = quill.clipboard.convert({
2386
+ html: '<style>.xl65{background-color:#FFC000}</style><table><tr><td class="xl65">text</td></tr></table>',
2387
+ });
2388
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2389
+ expect(cellOp).toBeDefined();
2390
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style || '';
2391
+ expect(style).not.toContain('background-color');
2392
+ });
2393
+
2394
+ it('pasteDefaultTagStyle=true enables tag selector styles', () => {
2395
+ const quill = createQuillWithTableModule(`<p><br></p>`, { pasteStyleSheet: true, pasteDefaultTagStyle: true });
2396
+ const delta = quill.clipboard.convert({
2397
+ html: '<style>td{background-color:#FFC000}</style><table><tr><td>text</td></tr></table>',
2398
+ });
2399
+ const cellOp = delta.ops.find(op => op.attributes?.['table-up-cell-inner']);
2400
+ expect(cellOp).toBeDefined();
2401
+ const style = (cellOp!.attributes!['table-up-cell-inner'] as any).style;
2402
+ expect(style).toContain('background-color');
2403
+ });
2404
+ });
@@ -0,0 +1,50 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { TableUp } from '../../table-up';
3
+ import { createQuillWithTableModule } from './utils';
4
+
5
+ beforeEach(() => {
6
+ vi.useFakeTimers();
7
+ });
8
+ afterEach(() => {
9
+ vi.useRealTimers();
10
+ });
11
+
12
+ describe('TableUp - refreshUI', () => {
13
+ it('should call texts function with key and use returned text', async () => {
14
+ let locale = 'en';
15
+ const keyCalls: string[] = [];
16
+ const quill = createQuillWithTableModule('<p><br></p>', {
17
+ texts: (key) => {
18
+ keyCalls.push(key);
19
+ if (key === 'customBtnText') return locale === 'en' ? 'Custom' : '自定义';
20
+ if (key === 'fullCheckboxText') return locale === 'en' ? 'Insert full width table' : '插入满宽表格';
21
+ return '';
22
+ },
23
+ });
24
+
25
+ const tableModule = quill.getModule(TableUp.moduleName) as TableUp;
26
+ expect(tableModule.options.texts.customBtnText).toBe('Custom');
27
+ expect(tableModule.options.texts.fullCheckboxText).toBe('Insert full width table');
28
+ expect(keyCalls).toContain('customBtnText');
29
+ expect(keyCalls).toContain('fullCheckboxText');
30
+ locale = 'zh';
31
+ await tableModule.refreshUI();
32
+ expect(tableModule.options.texts.customBtnText).toBe('自定义');
33
+ expect(tableModule.options.texts.fullCheckboxText).toBe('插入满宽表格');
34
+ });
35
+
36
+ it('should bind this to TableUp instance when calling texts function', () => {
37
+ let boundThis: TableUp | undefined;
38
+ const quill = createQuillWithTableModule('<p><br></p>', {
39
+ texts(key) {
40
+ boundThis = this;
41
+ if (key === 'customBtnText') return 'Custom';
42
+ return '';
43
+ },
44
+ });
45
+
46
+ const tableModule = quill.getModule(TableUp.moduleName) as TableUp;
47
+ expect(tableModule.options.texts.customBtnText).toBe('Custom');
48
+ expect(boundThis).toBe(tableModule);
49
+ });
50
+ });
@@ -1,5 +1,5 @@
1
1
  import type { Op, Delta as TypeDelta, Range as TypeRange } from 'quill';
2
- import type { TableCaptionValue, TableColValue, TableUpOptions } from '../../utils';
2
+ import type { TableCaptionValue, TableColValue, TableUpOptionsInput } from '../../utils';
3
3
  import Quill from 'quill';
4
4
  import { expect, vi } from 'vitest';
5
5
  import { TableUp } from '../../table-up';
@@ -41,7 +41,7 @@ export function sortAttributes(element: HTMLElement) {
41
41
  }
42
42
  });
43
43
  }
44
- export function createQuillWithTableModule(html: string, tableOptions: Partial<TableUpOptions> = {}, moduleOptions = {}, quillOptions = {}, register = {}) {
44
+ export function createQuillWithTableModule(html: string, tableOptions: TableUpOptionsInput = {}, moduleOptions = {}, quillOptions = {}, register = {}) {
45
45
  Quill.register({
46
46
  [`modules/${TableUp.moduleName}`]: TableUp,
47
47
  ...register,
@@ -1,10 +1,11 @@
1
1
  import type { Parchment as TypeParchment } from 'quill';
2
2
  import type { Delta as TypeDelta } from 'quill/core';
3
3
  import type TypeClipboard from 'quill/modules/clipboard';
4
- import type { TableCaptionValue, TableCellValue } from '../../utils';
4
+ import type { TableUp } from '../../table-up';
5
+ import type { TableCaptionValue, TableCellValue, TableUpOptions } from '../../utils';
5
6
  import Quill from 'quill';
6
7
  import { TableCellFormat, TableColFormat } from '../../formats';
7
- import { blotName, cssTextToObject, isObject, isString, objectToCssText, randomId, tableUpSize } from '../../utils';
8
+ import { blotName, cssTextToObject, isObject, isString, objectToCssText, randomId, resolveStyleSheetToInline, tableUpInternal, tableUpSize } from '../../utils';
8
9
 
9
10
  const Delta = Quill.import('delta');
10
11
  const Clipboard = Quill.import('modules/clipboard') as typeof TypeClipboard;
@@ -71,6 +72,17 @@ export class TableClipboard extends Clipboard {
71
72
  this.addMatcher(Node.ELEMENT_NODE, this.matchTdAttributor.bind(this));
72
73
  }
73
74
 
75
+ normalizeHTML(doc: Document) {
76
+ super.normalizeHTML(doc);
77
+ const tableModule = this.quill.getModule(tableUpInternal.moduleName) as TableUp | undefined;
78
+ const options: Partial<TableUpOptions> = tableModule?.options ?? {};
79
+ if (options.pasteStyleSheet !== false) {
80
+ resolveStyleSheetToInline(doc, {
81
+ includeDefaultTagStyle: options.pasteDefaultTagStyle,
82
+ });
83
+ }
84
+ }
85
+
74
86
  getStyleBackgroundColor(node: Node, delta: TypeDelta) {
75
87
  const backgroundColor = (node as HTMLElement).style.backgroundColor;
76
88
  if (backgroundColor) {
@@ -1 +1 @@
1
- {"version":"4.0.18","results":[[":__tests__/unit/utils.test-d.ts",{"duration":0,"failed":false}],[":__tests__/unit/table-insert.test.ts",{"duration":18173.0784,"failed":false}],[":__tests__/unit/table-clipboard.test.ts",{"duration":22002.459899999998,"failed":false}],[":__tests__/unit/table-blots.test.ts",{"duration":10285.912399999997,"failed":false}],[":__tests__/unit/table-cell-merge.test.ts",{"duration":12967.189000000002,"failed":false}],[":__tests__/unit/utils.test.ts",{"duration":3874.055199999999,"failed":false}],[":__tests__/unit/table-hack.test.ts",{"duration":9750.4833,"failed":false}],[":__tests__/unit/table-redo-undo.test.ts",{"duration":32718.328999999998,"failed":false}],[":__tests__/unit/table-caption.test.ts",{"duration":3572.148699999998,"failed":false}],[":__tests__/unit/table-remove.test.ts",{"duration":6382.709699999998,"failed":false}],[":__tests__/unit/table-contenteditable.test.ts",{"duration":1995.755299999999,"failed":false}]]}
1
+ {"version":"4.0.18","results":[[":__tests__/unit/utils.test-d.ts",{"duration":0,"failed":false}],[":__tests__/unit/table-clipboard.test.ts",{"duration":9695.598699999999,"failed":false}],[":__tests__/unit/table-redo-undo.test.ts",{"duration":17920.0041,"failed":false}],[":__tests__/unit/table-hack.test.ts",{"duration":3642.7681000000002,"failed":false}],[":__tests__/unit/table-cell-merge.test.ts",{"duration":4956.3197,"failed":false}],[":__tests__/unit/table-blots.test.ts",{"duration":4045.2762999999995,"failed":false}],[":__tests__/unit/table-insert.test.ts",{"duration":7073.0178,"failed":false}],[":__tests__/unit/utils.test.ts",{"duration":975.8723999999997,"failed":false}],[":__tests__/unit/table-caption.test.ts",{"duration":1513.6225000000004,"failed":false}],[":__tests__/unit/table-contenteditable.test.ts",{"duration":681.7878000000001,"failed":false}],[":__tests__/unit/table-remove.test.ts",{"duration":4171.0812,"failed":false}],[":__tests__/unit/table-refresh-ui.test.ts",{"duration":414.3294000000001,"failed":false}]]}
package/src/table-up.ts CHANGED
@@ -5,7 +5,7 @@ import type { Context } from 'quill/modules/keyboard';
5
5
  import type TypeKeyboard from 'quill/modules/keyboard';
6
6
  import type TypeToolbar from 'quill/modules/toolbar';
7
7
  import type { TableSelection } from './modules';
8
- import type { Constructor, QuillTheme, QuillThemePicker, TableBodyTag, TableCellValue, TableConstantsData, TableTextOptions, TableUpOptions } from './utils';
8
+ import type { Constructor, QuillTheme, QuillThemePicker, TableBodyTag, TableCellValue, TableConstantsData, TableTextOptions, TableTextOptionsInput, TableUpOptions, TableUpOptionsInput } from './utils';
9
9
  import Quill from 'quill';
10
10
  import { BlockEmbedOverride, BlockOverride, ContainerFormat, ScrollOverride, TableBodyFormat, TableCaptionFormat, TableCellFormat, TableCellInnerFormat, TableColFormat, TableColgroupFormat, TableFootFormat, TableHeadFormat, TableMainFormat, TableRowFormat, TableWrapperFormat } from './formats';
11
11
  import { TableClipboard } from './modules';
@@ -277,6 +277,7 @@ export class TableUp {
277
277
 
278
278
  quill: Quill;
279
279
  options: TableUpOptions;
280
+ textOptionsInput: TableTextOptionsInput | undefined;
280
281
  toolBox: HTMLDivElement;
281
282
  fixTableByLisenter = debounce(this.balanceTables, 100);
282
283
  selector?: HTMLElement;
@@ -288,33 +289,28 @@ export class TableUp {
288
289
  return this.constructor;
289
290
  }
290
291
 
291
- constructor(quill: Quill, options: Partial<TableUpOptions>) {
292
+ constructor(quill: Quill, options: TableUpOptionsInput) {
292
293
  this.quill = quill;
294
+ this.textOptionsInput = options?.texts;
293
295
  this.options = this.resolveOptions(options || {});
294
296
  this.toolBox = this.initialContainer();
295
297
 
296
- const toolbar = this.quill.getModule('toolbar') as TypeToolbar;
297
- if (toolbar && (this.quill.theme as QuillTheme).pickers) {
298
- const [, select] = (toolbar.controls as [string, HTMLElement][] || []).find(([name]) => name === this.statics.toolName) || [];
299
- if (select?.tagName.toLocaleLowerCase() === 'select') {
300
- const picker = (this.quill.theme as QuillTheme).pickers.find(picker => picker.select === select);
301
- if (picker) {
302
- picker.label.innerHTML = this.options.icon;
303
- this.buildCustomSelect(this.options.customSelect, picker);
304
- picker.label.addEventListener('mousedown', () => {
305
- if (!this.selector || !picker) return;
306
- const selectRect = this.selector.getBoundingClientRect();
307
- const { leftLimited } = limitDomInViewPort(selectRect);
308
- if (leftLimited) {
309
- const labelRect = picker.label.getBoundingClientRect();
310
- Object.assign(picker.options.style, { transform: `translateX(calc(-100% + ${labelRect.width}px))` });
311
- }
312
- else {
313
- Object.assign(picker.options.style, { transform: undefined });
314
- }
315
- });
298
+ const picker = this.getToolbarPicker();
299
+ if (picker) {
300
+ picker.label.innerHTML = this.options.icon;
301
+ this.buildCustomSelect(this.options.customSelect, picker);
302
+ picker.label.addEventListener('mousedown', () => {
303
+ if (!this.selector) return;
304
+ const selectRect = this.selector.getBoundingClientRect();
305
+ const { leftLimited } = limitDomInViewPort(selectRect);
306
+ if (leftLimited) {
307
+ const labelRect = picker.label.getBoundingClientRect();
308
+ Object.assign(picker.options.style, { transform: `translateX(calc(-100% + ${labelRect.width}px))` });
316
309
  }
317
- }
310
+ else {
311
+ Object.assign(picker.options.style, { transform: undefined });
312
+ }
313
+ });
318
314
  }
319
315
 
320
316
  const keyboard = this.quill.getModule('keyboard') as TypeKeyboard;
@@ -367,20 +363,31 @@ export class TableUp {
367
363
  }
368
364
  }
369
365
 
370
- resolveOptions(options: Partial<TableUpOptions>): TableUpOptions {
366
+ getToolbarPicker() {
367
+ const toolbar = this.quill.getModule('toolbar') as TypeToolbar;
368
+ if (!toolbar || !(this.quill.theme as QuillTheme).pickers) return;
369
+ const [, select] = (toolbar.controls as [string, HTMLElement][] || []).find(([name]) => name === this.statics.toolName) || [];
370
+ if (select?.tagName.toLocaleLowerCase() !== 'select') return;
371
+ return (this.quill.theme as QuillTheme).pickers.find(picker => picker.select === select);
372
+ }
373
+
374
+ resolveOptions(options: TableUpOptionsInput): TableUpOptions {
375
+ const { texts, ...rest } = options;
371
376
  return Object.assign({
372
377
  customBtn: false,
373
- texts: this.resolveTexts(options.texts || {}),
378
+ texts: this.resolveTexts(texts),
374
379
  full: false,
375
380
  fullSwitch: true,
376
381
  icon: icons.table,
377
382
  autoMergeCell: true,
383
+ pasteStyleSheet: false,
384
+ pasteDefaultTagStyle: false,
378
385
  modules: [],
379
- } as TableUpOptions, options);
386
+ } as TableUpOptions, rest);
380
387
  }
381
388
 
382
- resolveTexts(options: Partial<TableTextOptions>) {
383
- return Object.assign({
389
+ resolveTexts(options?: TableTextOptionsInput) {
390
+ const defaults: TableTextOptions = {
384
391
  fullCheckboxText: 'Insert full width table',
385
392
  customBtnText: 'Custom',
386
393
  confirmText: 'Confirm',
@@ -405,7 +412,35 @@ export class TableUp {
405
412
  DeleteTable: 'Delete table',
406
413
  BackgroundColor: 'Set background color',
407
414
  BorderColor: 'Set border color',
408
- }, options);
415
+ };
416
+ if (isFunction(options)) {
417
+ const textGetter = options;
418
+ const tableModule = this;
419
+ return new Proxy(defaults, {
420
+ get(target, key: string | symbol) {
421
+ if (typeof key !== 'string') return Reflect.get(target, key);
422
+ const value = textGetter.call(tableModule, key);
423
+ return isString(value) ? value : (target as Record<string, string>)[key];
424
+ },
425
+ });
426
+ }
427
+ return Object.assign(defaults, options);
428
+ }
429
+
430
+ async refreshUI() {
431
+ this.options.texts = this.resolveTexts(this.textOptionsInput);
432
+
433
+ const picker = this.getToolbarPicker();
434
+ if (picker) {
435
+ picker.label.innerHTML = this.options.icon;
436
+ await this.buildCustomSelect(this.options.customSelect, picker);
437
+ }
438
+
439
+ for (const module of Object.values(this.modules)) {
440
+ if (module && typeof (module as any).hide === 'function') {
441
+ (module as any).hide();
442
+ }
443
+ }
409
444
  }
410
445
 
411
446
  initModules() {
@@ -8,7 +8,7 @@ export * from './is';
8
8
  export * from './position';
9
9
  export * from './resize-observer-helper';
10
10
  export * from './scroll';
11
- export * from './style-helper';
11
+ export * from './style';
12
12
  export * from './transformer';
13
13
  export * from './transition-event-helper';
14
14
  export * from './types';
@@ -1,4 +1,4 @@
1
- import { toCamelCase, toKebabCase } from './transformer';
1
+ import { toCamelCase, toKebabCase } from '../transformer';
2
2
 
3
3
  export function getInlineStyles(domNode: HTMLElement): Record<string, string> {
4
4
  const inlineStyles: Record<string, string> = {};
@@ -0,0 +1,2 @@
1
+ export * from './helper';
2
+ export * from './inline-core';
@@ -0,0 +1,80 @@
1
+ interface CSSRule {
2
+ selector: string;
3
+ styles: Record<string, string>;
4
+ }
5
+
6
+ export function parseCSSRules(cssText: string): CSSRule[] {
7
+ const rules: CSSRule[] = [];
8
+ // Remove CSS comments
9
+ const cleaned = cssText.replaceAll(/\/\*[\s\S]*?\*\//g, '');
10
+ // Match selector { declarations } blocks
11
+ const ruleRegex = /([^{}]+)\{([^{}]*)\}/g;
12
+ let match;
13
+ while ((match = ruleRegex.exec(cleaned)) !== null) {
14
+ const selector = match[1].trim();
15
+ const declarations = match[2].trim();
16
+ // Skip @-rules
17
+ if (selector.startsWith('@')) continue;
18
+ const styles: Record<string, string> = {};
19
+ for (const decl of declarations.split(';')) {
20
+ const colonIndex = decl.indexOf(':');
21
+ if (colonIndex === -1) continue;
22
+ const prop = decl.slice(0, colonIndex).trim();
23
+ const value = decl.slice(colonIndex + 1).trim();
24
+ if (prop && value) {
25
+ styles[prop] = value;
26
+ }
27
+ }
28
+ if (Object.keys(styles).length > 0) {
29
+ rules.push({ selector, styles });
30
+ }
31
+ }
32
+ return rules;
33
+ }
34
+
35
+ // A selector is tag-only if it contains no class (.), id (#),
36
+ // attribute ([), or pseudo (:) indicators — meaning it's composed
37
+ // solely of tag names and combinators (space, >, +, ~).
38
+ // e.g. "td", "table td", "tr > td", "table > tbody > tr > td"
39
+ export function isTagOnlySelector(selector: string): boolean {
40
+ return !/[.#\[:]/.test(selector);
41
+ }
42
+
43
+ export interface ResolveStyleSheetOptions {
44
+ includeDefaultTagStyle?: boolean;
45
+ }
46
+
47
+ export function resolveStyleSheetToInline(doc: Document, options?: ResolveStyleSheetOptions): void {
48
+ const includeDefaultTagStyle = options?.includeDefaultTagStyle ?? false;
49
+ const styleElements = doc.querySelectorAll('style');
50
+ for (const styleEl of Array.from(styleElements)) {
51
+ const rules = parseCSSRules(styleEl.textContent || '');
52
+ for (const rule of rules) {
53
+ // A single selector string may contain comma-separated selectors
54
+ const selectors = rule.selector.split(',').map(s => s.trim()).filter(Boolean);
55
+ for (const selector of selectors) {
56
+ // Skip tag-only selectors (e.g. "td", "table td", "tr > td") unless explicitly allowed
57
+ if (!includeDefaultTagStyle && isTagOnlySelector(selector)) {
58
+ continue;
59
+ }
60
+ let elements: NodeListOf<Element>;
61
+ try {
62
+ elements = doc.querySelectorAll(selector);
63
+ }
64
+ catch {
65
+ // Skip invalid selectors
66
+ continue;
67
+ }
68
+ for (const el of Array.from(elements)) {
69
+ const htmlEl = el as HTMLElement;
70
+ for (const [prop, value] of Object.entries(rule.styles)) {
71
+ // Inline style takes priority — only set if not already present
72
+ if (!htmlEl.style.getPropertyValue(prop)) {
73
+ htmlEl.style.setProperty(prop, value);
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
@@ -57,6 +57,7 @@ export interface TableTextOptions extends TableCreatorTextOptions, TableMenuText
57
57
  transparent: string;
58
58
  perWidthInsufficient: string;
59
59
  }
60
+ export type TableTextOptionsInput = Partial<TableTextOptions> | ((this: TableUp, key: string) => string);
60
61
  export interface TableUpExtraModule extends Constructor<any, [TableUp, Quill, any]> {
61
62
  moduleName: string;
62
63
  }
@@ -72,8 +73,13 @@ export interface TableUpOptions {
72
73
  texts: TableTextOptions;
73
74
  icon: string;
74
75
  autoMergeCell: boolean;
76
+ pasteStyleSheet: boolean;
77
+ pasteDefaultTagStyle: boolean;
75
78
  modules: TableUpModule[];
76
79
  }
80
+ export interface TableUpOptionsInput extends Partial<Omit<TableUpOptions, 'texts'>> {
81
+ texts?: TableTextOptionsInput;
82
+ }
77
83
  export interface TableColValue {
78
84
  tableId: string;
79
85
  colId: string;