Skip to content

Commit

Permalink
feat: table cell scale
Browse files Browse the repository at this point in the history
  • Loading branch information
zzxming committed Dec 14, 2024
1 parent 5a0b5cc commit 4b4725a
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 45 deletions.
41 changes: 25 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ npm install quill-table-up

```js
import Quill from 'quill';
import TableUp, { TableAlign, TableMenuContextmenu, TableResizeBox, TableSelection, TableVirtualScrollbar } from 'quill-table-up';
import TableUp, { TableAlign, TableMenuContextmenu, TableResizeBox, TableResizeScale, TableSelection, TableVirtualScrollbar } from 'quill-table-up';
import 'quill/dist/quill.snow.css';
import 'quill-table-up/index.css';
// If using the default customSelect option. You need to import this css
Expand All @@ -47,6 +47,7 @@ const quill = new Quill('#editor', {
scrollbar: TableVirtualScrollbar,
align: TableAlign,
resize: TableResizeBox,
resizeScale: TableResizeScale,
customSelect: defaultCustomSelect,
selection: TableSelection,
selectionOptions: {
Expand All @@ -61,21 +62,23 @@ const quill = new Quill('#editor', {

### TableUp Options

| attribute | description | type | default |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------- |
| full | if set `true`. width max will be 100% | `boolean` | `false` |
| texts | the text used to create the table | `TableTextOptions` | `defaultTexts` |
| customSelect | display a custom select to custom row and column number add a table. module provides default selector `defaultCustomSelect` | `(tableModule: TableUp, picker: Picker) => Promise<HTMLElement> \| HTMLElement` | - |
| customBtn | display a custom button to custom row and column number add a table. it only when use `defaultCustomSelect` will effect | `boolean` | `false` |
| selection | table selection handler. module provides `TableSelection` | `Constructor` | - |
| selectionOptions | table selection options | `TableSelectionOptions` | - |
| icon | picker svg icon string. it will set with `innerHTML` | `string` | `origin table icon` |
| resize | table cell resize handler. module provides `TableResizeLine` and `TableResizeBox` | `Constructor` | - |
| scrollbar | table virtual scrollbar handler. module provides `TableVirtualScrollbar` | `Constructor` | - |
| align | table alignment handler. module provides `TableAlign` | `Constructor` | - |
| resizeOptions | table cell resize handler options | `any` | - |
| alignOptions | table alignment handler options | `any` | - |
| scrollbarOptions | table virtual scrollbar handler options | `any` | - |
| attribute | description | type | default |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------- |
| full | if set `true`. width max will be 100% | `boolean` | `false` |
| texts | the text used to create the table | `TableTextOptions` | `defaultTexts` |
| customSelect | display a custom select to custom row and column number add a table. module provides default selector `defaultCustomSelect` | `(tableModule: TableUp, picker: Picker) => Promise<HTMLElement> \| HTMLElement` | - |
| customBtn | display a custom button to custom row and column number add a table. it only when use `defaultCustomSelect` will effect | `boolean` | `false` |
| selection | table selection handler. module provides `TableSelection` | `Constructor` | - |
| selectionOptions | table selection options | `TableSelectionOptions` | - |
| icon | picker svg icon string. it will set with `innerHTML` | `string` | `origin table icon` |
| resize | table cell resize handler. module provides `TableResizeLine` and `TableResizeBox` | `Constructor` | - |
| resizeScale | equal scale table cell handler. module provides `TableResizeScale` | `Constructor` | - |
| scrollbar | table virtual scrollbar handler. module provides `TableVirtualScrollbar` | `Constructor` | - |
| align | table alignment handler. module provides `TableAlign` | `Constructor` | - |
| resizeOptions | table cell resize handler options | `any` | - |
| resizeScaleOptions | equal scale table cell handler options | `TableResizeScaleOptions` | - |
| alignOptions | table alignment handler options | `any` | - |
| scrollbarOptions | table virtual scrollbar handler options | `any` | - |

> I'm not suggest to use `TableVirtualScrollbar` and `TableResizeLine` at same time, because it will make the virtual scrollbar display blink. Just like the first editor in [demo](https://zzxming.github.io/quill-table-up/)
Expand All @@ -99,6 +102,12 @@ const defaultTexts = {

</details>

### TableResizeScale Options

| attribute | description | type | default |
| --------- | ------------------------ | -------- | ------- |
| blockSize | resize handle block size | `number` | `12` |

### TableSelection Options

| attribute | description | type | default |
Expand Down
15 changes: 14 additions & 1 deletion docs/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
/* eslint-disable no-undef */
const Quill = window.Quill;
const { default: TableUp, TableAlign, TableVirtualScrollbar, TableResizeLine, TableResizeBox, TableMenuContextmenu, TableMenuSelect, defaultCustomSelect, TableSelection } = window.TableUp;
const {
default: TableUp,
TableAlign,
TableVirtualScrollbar,
TableResizeLine,
TableResizeBox,
TableMenuContextmenu,
TableMenuSelect,
TableResizeScale,
defaultCustomSelect,
TableSelection,
} = window.TableUp;

Quill.register({
[`modules/${TableUp.moduleName}`]: TableUp,
Expand Down Expand Up @@ -33,6 +44,7 @@ const quill1 = new Quill('#editor1', {
scrollbar: TableVirtualScrollbar,
align: TableAlign,
resize: TableResizeLine,
resizeScale: TableResizeScale,
customSelect: defaultCustomSelect,
customBtn: true,
selection: TableSelection,
Expand Down Expand Up @@ -109,6 +121,7 @@ const quill2 = new Quill('#editor2', {
scrollbar: TableVirtualScrollbar,
align: TableAlign,
resize: TableResizeBox,
resizeScale: TableResizeScale,
customSelect: defaultCustomSelect,
selection: TableSelection,
selectionOptions: {
Expand Down
9 changes: 9 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ export class TableUp {
tableResize?: InternalModule;
tableScrollbar?: InternalModule;
tableAlign?: InternalModule;
tableResizeScale?: InternalModule;

get statics(): any {
return this.constructor;
}
Expand Down Expand Up @@ -346,6 +348,7 @@ export class TableUp {
alignOptions: {},
scrollbarOptions: {},
resizeOptions: {},
resizeScaleOptions: {},
} as TableUpOptions, options);
};

Expand Down Expand Up @@ -529,6 +532,9 @@ export class TableUp {
if (this.options.resize) {
this.tableResize = new this.options.resize(this, table, quill, this.options.resizeOptions);
}
if (this.options.resizeScale) {
this.tableResizeScale = new this.options.resizeScale(this, table, quill, this.options.resizeScaleOptions);
}
}
}

Expand All @@ -549,6 +555,9 @@ export class TableUp {
this.tableResize.destroy();
this.tableResize = undefined;
}
if (this.tableResizeScale) {
this.tableResizeScale.destroy();
}
this.table = undefined;
}

Expand Down
2 changes: 1 addition & 1 deletion src/modules/table-align.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class TableAlign {
this.show();
computePosition(this.tableWrapperBlot.domNode, this.alignBox, {
placement: 'top',
middleware: [flip(), shift({ limiter: limitShift() }), offset(8)],
middleware: [flip(), shift({ limiter: limitShift() }), offset(16)],
}).then(({ x, y }) => {
Object.assign(this.alignBox!.style, {
left: `${x}px`,
Expand Down
1 change: 1 addition & 0 deletions src/modules/table-resize/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './table-resize-box';
export * from './table-resize-common';
export * from './table-resize-line';
export * from './table-resize-scale';
export * from './utils';
143 changes: 143 additions & 0 deletions src/modules/table-resize/table-resize-scale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import type TableUp from '../..';
import type { TableColFormat, TableMainFormat, TableRowFormat, TableWrapperFormat } from '../../formats';
import type { TableResizeScaleOptions } from '../../utils';
import Quill from 'quill';
import { addScrollEvent, clearScrollEvent, createBEM, tableUpSize } from '../../utils';

export class TableResizeScale {
scrollHandler: [HTMLElement, (e: Event) => void][] = [];
tableMainBlot: TableMainFormat | null = null;
tableWrapperBlot: TableWrapperFormat | null = null;
bem = createBEM('scale');
startX: number = 0;
startY: number = 0;
options: TableResizeScaleOptions;
root?: HTMLElement;
block?: HTMLElement;
resizeobserver: ResizeObserver = new ResizeObserver(() => this.update());
constructor(public tableModule: TableUp, table: HTMLElement, public quill: Quill, options: Partial<TableResizeScaleOptions>) {
this.options = this.resolveOptions(options);
this.tableMainBlot = Quill.find(table) as TableMainFormat;

if (this.tableMainBlot && !this.tableMainBlot.full) {
this.tableWrapperBlot = this.tableMainBlot.parent as TableWrapperFormat;
this.buildResizer();
this.show();
}
}

resolveOptions(options: Partial<TableResizeScaleOptions>) {
return Object.assign({
blockSize: 12,
}, options);
}

buildResizer() {
if (!this.tableMainBlot || !this.tableWrapperBlot) return;
this.root = this.tableModule.addContainer(this.bem.b());
this.root.classList.add(this.bem.is('hidden'));
this.block = document.createElement('div');
this.block.classList.add(this.bem.be('block'));
Object.assign(this.block.style, {
width: `${this.options.blockSize}px`,
height: `${this.options.blockSize}px`,
});
this.root.appendChild(this.block);

let originColWidth: { blot: TableColFormat; width: number }[] = [];
let originRowHeight: { blot: TableRowFormat; height: number }[] = [];
const handleMouseMove = (e: MouseEvent) => {
// divide equally by col count/row count
const diffX = e.clientX - this.startX;
const diffY = e.clientY - this.startY;
const itemWidth = Math.floor(diffX / originColWidth.length);
const itemHeight = Math.floor(diffY / originRowHeight.length);

for (const { blot, width } of originColWidth) {
blot.width = Math.max(width + itemWidth, tableUpSize.colMinWidthPx);
}
for (const { blot, height } of originRowHeight) {
blot.setHeight(`${Math.max(height + itemHeight, tableUpSize.rowMinHeightPx)}px`);
}
};
const handleMouseUp = () => {
originColWidth = [];
originRowHeight = [];
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
this.block.addEventListener('mousedown', (e) => {
if (!this.tableMainBlot || this.isTableOutofEditor()) return;
this.startX = e.clientX;
this.startY = e.clientY;
// save the origin width and height to calculate result width and height
originColWidth = this.tableMainBlot.getCols().map(col => ({ blot: col, width: col.width }));
originRowHeight = this.tableMainBlot.getRows().map(row => ({ blot: row, height: row.domNode.getBoundingClientRect().height }));
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
});
this.block.addEventListener('dragstart', e => e.preventDefault());

this.resizeobserver.observe(this.tableMainBlot.domNode);
addScrollEvent.call(this, this.quill.root, () => this.update());
addScrollEvent.call(this, this.tableWrapperBlot.domNode, () => this.update());
}

isTableOutofEditor(): boolean {
if (!this.tableMainBlot || !this.tableWrapperBlot || this.tableMainBlot.full) return false;
// if tableMain width larger than tableWrapper. reset tableMain width equal editor width
const tableRect = this.tableMainBlot.domNode.getBoundingClientRect();
const tableWrapperRect = this.tableWrapperBlot.domNode.getBoundingClientRect();
// equal scale
if (tableRect.width > tableWrapperRect.width) {
for (const col of this.tableMainBlot.getCols()) {
col.width = Math.floor((col.width / tableRect.width) * tableWrapperRect.width);
}
this.tableMainBlot.colWidthFillTable();
return true;
}
return false;
}

update() {
if (!this.block || !this.root || !this.tableMainBlot || !this.tableWrapperBlot) return false;
const tableRect = this.tableMainBlot.domNode.getBoundingClientRect();
const tableWrapperRect = this.tableWrapperBlot.domNode.getBoundingClientRect();
const editorRect = this.quill.root.getBoundingClientRect();
const { scrollTop, scrollLeft } = this.tableWrapperBlot.domNode;
const blockSize = this.options.blockSize * 2;
const rootWidth = Math.min(tableRect.width, tableWrapperRect.width) + blockSize;
const rootHeight = Math.min(tableRect.height, tableWrapperRect.height) + blockSize;
Object.assign(this.root.style, {
width: `${rootWidth}px`,
height: `${rootHeight}px`,
left: `${tableWrapperRect.x - editorRect.x - this.options.blockSize}px`,
top: `${tableWrapperRect.y - editorRect.y - this.options.blockSize}px`,
});
Object.assign(this.block.style, {
left: `${tableRect.width + blockSize - scrollLeft}px`,
top: `${rootHeight - scrollTop}px`,
});
}

show() {
if (this.root) {
this.root.classList.remove(this.bem.is('hidden'));
this.update();
}
}

hide() {
if (this.root) {
this.root.classList.add(this.bem.is('hidden'));
}
}

destroy() {
this.hide();
if (this.root) {
this.root.remove();
}
clearScrollEvent.call(this);
}
}
23 changes: 1 addition & 22 deletions src/modules/table-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { TableCellInnerFormat, TableMainFormat } from '../formats';
import type { InternalModule, RelactiveRect, TableSelectionOptions } from '../utils';
import Quill from 'quill';
import { TableCellFormat } from '../formats';
import { addScrollEvent, clearScrollEvent } from '../utils';
import { addScrollEvent, clearScrollEvent, getRelativeRect, isRectanglesIntersect } from '../utils';

const ERROR_LIMIT = 2;
export class TableSelection {
Expand Down Expand Up @@ -259,24 +259,3 @@ export class TableSelection {
return null;
}
}

function isRectanglesIntersect(a: Omit<RelactiveRect, 'width' | 'height'>, b: Omit<RelactiveRect, 'width' | 'height'>, tolerance = 4) {
const { x: minAx, y: minAy, x1: maxAx, y1: maxAy } = a;
const { x: minBx, y: minBy, x1: maxBx, y1: maxBy } = b;
const notOverlapX = maxAx <= minBx + tolerance || minAx + tolerance >= maxBx;
const notOverlapY = maxAy <= minBy + tolerance || minAy + tolerance >= maxBy;
return !(notOverlapX || notOverlapY);
}

function getRelativeRect(targetRect: Omit<RelactiveRect, 'x1' | 'y1'>, container: HTMLElement) {
const containerRect = container.getBoundingClientRect();

return {
x: targetRect.x - containerRect.x - container.scrollLeft,
y: targetRect.y - containerRect.y - container.scrollTop,
x1: targetRect.x - containerRect.x - container.scrollLeft + targetRect.width,
y1: targetRect.y - containerRect.y - container.scrollTop + targetRect.height,
width: targetRect.width,
height: targetRect.height,
};
}
3 changes: 3 additions & 0 deletions src/style/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@

@import './tooltip.less';
@import './dialog.less';

@import './color-picker.less';

.@{namespace}-tooltip {
Expand All @@ -442,3 +443,5 @@
padding: 8px 0px;
}
}

@import './table-resize-scale.less';
Loading

0 comments on commit 4b4725a

Please sign in to comment.