@jackuait/blok 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blok.mjs +2 -2
- package/dist/chunks/{blok-D-7DpjTs.mjs → blok-3RuPZd3G.mjs} +1205 -1172
- package/dist/chunks/{constants-DXYRzX7f.mjs → constants-BkelccB1.mjs} +185 -164
- package/dist/chunks/{tools-Chd7Auwx.mjs → tools-rsbC2UUN.mjs} +31 -12
- package/dist/full.mjs +3 -3
- package/dist/react.mjs +2 -2
- package/dist/tools.mjs +2 -2
- package/package.json +1 -1
- package/src/components/inline-tools/inline-tool-marker.ts +11 -0
- package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +1 -9
- package/src/components/modules/caret.ts +13 -1
- package/src/components/modules/paste/google-docs-preprocessor.ts +96 -38
- package/src/components/modules/toolbar/blockSettings.ts +1 -1
- package/src/components/modules/toolbar/index.ts +10 -1
- package/src/components/modules/toolbar/inline/index.ts +24 -2
- package/src/components/selection/cursor.ts +7 -0
- package/src/components/ui/toolbox.ts +14 -0
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +1 -1
- package/src/components/utils/popover/components/search-input/search-input.const.ts +1 -0
- package/src/components/utils/popover/components/search-input/search-input.ts +32 -1
- package/src/components/utils/popover/popover-desktop.ts +9 -1
- package/src/components/utils/popover/popover-inline.ts +8 -0
- package/src/styles/main.css +39 -11
- package/src/tools/paragraph/index.ts +3 -5
- package/src/tools/table/index.ts +70 -0
- package/src/tools/table/table-cell-blocks.ts +15 -3
- package/src/tools/table/table-cell-clipboard.ts +32 -5
package/src/styles/main.css
CHANGED
|
@@ -37,9 +37,11 @@
|
|
|
37
37
|
`all: initial !important` — main editor wrapper (outermost boundary).
|
|
38
38
|
Resets every property to its CSS initial value with !important priority,
|
|
39
39
|
blocking even `!important` host-page styles from cascading in.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
However, `!important` declarations always beat normal declarations
|
|
41
|
+
regardless of specificity or source order, so Tailwind utility classes
|
|
42
|
+
applied to the wrapper element itself (e.g. `relative`, `z-1`) are
|
|
43
|
+
overridden. Properties that the wrapper needs must be explicitly
|
|
44
|
+
re-applied with `!important` below (same pattern as font-family/color).
|
|
43
45
|
|
|
44
46
|
[data-blok-popover]:not([data-blok-popover-inline])
|
|
45
47
|
Inherited-properties-only reset — toolbox/settings popovers, appended
|
|
@@ -72,6 +74,27 @@
|
|
|
72
74
|
*/
|
|
73
75
|
[data-blok-interface=blok] {
|
|
74
76
|
all: initial !important;
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
Re-apply layout properties that `all: initial` resets.
|
|
80
|
+
The wrapper must be a positioned containing block so that
|
|
81
|
+
absolutely-positioned children (inline toolbar, main toolbar)
|
|
82
|
+
resolve against it rather than the document root. Without this,
|
|
83
|
+
the inline toolbar renders off-screen when the page is scrolled.
|
|
84
|
+
*/
|
|
85
|
+
position: relative !important;
|
|
86
|
+
box-sizing: border-box !important;
|
|
87
|
+
display: block !important;
|
|
88
|
+
z-index: 1 !important;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/*
|
|
92
|
+
RTL direction is set conditionally via JS (ui.ts) using the
|
|
93
|
+
data-blok-rtl attribute. `all: initial` resets direction to `ltr`,
|
|
94
|
+
so we must re-apply it with !important when the attribute is present.
|
|
95
|
+
*/
|
|
96
|
+
[data-blok-interface=blok][data-blok-rtl=true] {
|
|
97
|
+
direction: rtl !important;
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
/* Font family — user-configurable via config.style.fontFamily */
|
|
@@ -327,8 +350,9 @@
|
|
|
327
350
|
resize: vertical;
|
|
328
351
|
}
|
|
329
352
|
|
|
330
|
-
/* Remove inner padding in Chrome and Safari on macOS. */
|
|
331
|
-
:where([data-blok-interface], [data-blok-popover]) ::-webkit-search-decoration
|
|
353
|
+
/* Remove inner padding and native cancel button in Chrome and Safari on macOS. */
|
|
354
|
+
:where([data-blok-interface], [data-blok-popover]) ::-webkit-search-decoration,
|
|
355
|
+
:where([data-blok-interface], [data-blok-popover]) ::-webkit-search-cancel-button {
|
|
332
356
|
-webkit-appearance: none;
|
|
333
357
|
}
|
|
334
358
|
|
|
@@ -394,10 +418,13 @@
|
|
|
394
418
|
outline: none;
|
|
395
419
|
}
|
|
396
420
|
|
|
397
|
-
/* Never show a focus outline on
|
|
398
|
-
is sufficient affordance regardless of how focus was triggered.
|
|
399
|
-
|
|
400
|
-
|
|
421
|
+
/* Never show a focus outline on text-entry elements — the text cursor
|
|
422
|
+
is sufficient affordance regardless of how focus was triggered.
|
|
423
|
+
Browsers always match :focus-visible on these elements even on mouse
|
|
424
|
+
click (per CSS Selectors L4), so the :focus-visible restore rule below
|
|
425
|
+
would otherwise override element-level outline-hidden utilities. */
|
|
426
|
+
[data-blok-interface] :is([contenteditable], input, textarea):focus-visible,
|
|
427
|
+
[data-blok-popover] :is([contenteditable], input, textarea):focus-visible {
|
|
401
428
|
outline: none;
|
|
402
429
|
}
|
|
403
430
|
|
|
@@ -952,8 +979,9 @@
|
|
|
952
979
|
* When the user types "/" to open the toolbox, the contenteditable
|
|
953
980
|
* transforms to look like a search input with a placeholder.
|
|
954
981
|
*/
|
|
955
|
-
[data-blok-slash-search]
|
|
956
|
-
|
|
982
|
+
[data-blok-slash-search],
|
|
983
|
+
[data-blok-slash-search]:focus-visible {
|
|
984
|
+
@apply bg-search-input-bg rounded-[4px] transition-colors duration-150 max-w-[240px];
|
|
957
985
|
}
|
|
958
986
|
|
|
959
987
|
[data-blok-slash-search]::after {
|
|
@@ -346,11 +346,9 @@ export class Paragraph implements BlockTool {
|
|
|
346
346
|
|
|
347
347
|
this._data = data;
|
|
348
348
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
});
|
|
349
|
+
if (this._element) {
|
|
350
|
+
this._element.innerHTML = this._data.text || '';
|
|
351
|
+
}
|
|
354
352
|
}
|
|
355
353
|
|
|
356
354
|
/**
|
package/src/tools/table/index.ts
CHANGED
|
@@ -1395,6 +1395,20 @@ export class Table implements BlockTool {
|
|
|
1395
1395
|
return;
|
|
1396
1396
|
}
|
|
1397
1397
|
|
|
1398
|
+
/**
|
|
1399
|
+
* Single-cell (1×1) payloads should insert content inline at the caret
|
|
1400
|
+
* position rather than replacing the entire target cell. This matches user
|
|
1401
|
+
* expectations: copying one cell and pasting into another cell (or the same
|
|
1402
|
+
* cell) appends/inserts the text instead of overwriting.
|
|
1403
|
+
*/
|
|
1404
|
+
if (payload.rows === 1 && payload.cols === 1) {
|
|
1405
|
+
e.preventDefault();
|
|
1406
|
+
e.stopPropagation();
|
|
1407
|
+
this.insertSingleCellPayloadInline(payload.cells[0][0]);
|
|
1408
|
+
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1398
1412
|
e.preventDefault();
|
|
1399
1413
|
e.stopPropagation();
|
|
1400
1414
|
|
|
@@ -1406,6 +1420,62 @@ export class Table implements BlockTool {
|
|
|
1406
1420
|
this.pastePayloadIntoCells(gridEl, payload, targetRowIndex, targetColIndex);
|
|
1407
1421
|
}
|
|
1408
1422
|
|
|
1423
|
+
/**
|
|
1424
|
+
* Insert the content of a single clipboard cell at the current caret position.
|
|
1425
|
+
* Extracts text from each block and joins with line breaks.
|
|
1426
|
+
*/
|
|
1427
|
+
private insertSingleCellPayloadInline(cell: { blocks: ClipboardBlockData[] }): void {
|
|
1428
|
+
const html = cell.blocks
|
|
1429
|
+
.map((block) => {
|
|
1430
|
+
if (typeof block.data.text === 'string') {
|
|
1431
|
+
return block.data.text;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
return '';
|
|
1435
|
+
})
|
|
1436
|
+
.filter(Boolean)
|
|
1437
|
+
.join('<br>');
|
|
1438
|
+
|
|
1439
|
+
if (!html) {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
const selection = window.getSelection();
|
|
1444
|
+
|
|
1445
|
+
if (!selection || selection.rangeCount === 0) {
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
const range = selection.getRangeAt(0);
|
|
1450
|
+
|
|
1451
|
+
range.deleteContents();
|
|
1452
|
+
|
|
1453
|
+
const fragment = document.createDocumentFragment();
|
|
1454
|
+
const wrapper = document.createElement('div');
|
|
1455
|
+
|
|
1456
|
+
wrapper.innerHTML = html;
|
|
1457
|
+
|
|
1458
|
+
Array.from(wrapper.childNodes).forEach((child) => fragment.appendChild(child));
|
|
1459
|
+
|
|
1460
|
+
if (fragment.childNodes.length === 0) {
|
|
1461
|
+
fragment.appendChild(new Text());
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const lastChild = fragment.lastChild as ChildNode;
|
|
1465
|
+
|
|
1466
|
+
range.insertNode(fragment);
|
|
1467
|
+
|
|
1468
|
+
const newRange = document.createRange();
|
|
1469
|
+
const nodeToSetCaret = lastChild.nodeType === Node.TEXT_NODE ? lastChild : lastChild.firstChild;
|
|
1470
|
+
|
|
1471
|
+
if (nodeToSetCaret !== null && nodeToSetCaret.textContent !== null) {
|
|
1472
|
+
newRange.setStart(nodeToSetCaret, nodeToSetCaret.textContent.length);
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
selection.removeAllRanges();
|
|
1476
|
+
selection.addRange(newRange);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1409
1479
|
private pastePayloadIntoCells(
|
|
1410
1480
|
gridEl: HTMLElement,
|
|
1411
1481
|
payload: TableCellsClipboard,
|
|
@@ -144,8 +144,8 @@ export class TableCellBlocks {
|
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
// ArrowDown at last row -> exit table
|
|
148
|
-
if (event.key === 'ArrowDown' && position.row === this.getRowCount() - 1) {
|
|
147
|
+
// ArrowDown at last row -> exit table (skip if already handled by block-level navigation)
|
|
148
|
+
if (event.key === 'ArrowDown' && !event.defaultPrevented && position.row === this.getRowCount() - 1) {
|
|
149
149
|
event.preventDefault();
|
|
150
150
|
this.exitTableForward();
|
|
151
151
|
}
|
|
@@ -492,7 +492,19 @@ export class TableCellBlocks {
|
|
|
492
492
|
return;
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
-
|
|
495
|
+
// Insert at the correct DOM position based on the flat array order,
|
|
496
|
+
// so that pressing Enter on a non-last paragraph inserts the new block
|
|
497
|
+
// right after the current one instead of always at the end of the cell.
|
|
498
|
+
const blocksCount = this.api.blocks.getBlocksCount();
|
|
499
|
+
const nextSiblingHolder = Array.from(
|
|
500
|
+
{ length: blocksCount - index - 1 },
|
|
501
|
+
(_, offset) => this.api.blocks.getBlockByIndex(index + 1 + offset)
|
|
502
|
+
).find(
|
|
503
|
+
candidate => candidate?.holder.parentElement === container
|
|
504
|
+
)?.holder ?? null;
|
|
505
|
+
|
|
506
|
+
// insertBefore(el, null) is equivalent to appendChild
|
|
507
|
+
container.insertBefore(block.holder, nextSiblingHolder);
|
|
496
508
|
this.api.blocks.setBlockParent(blockId, this.tableBlockId);
|
|
497
509
|
this.stripPlaceholders(container);
|
|
498
510
|
}
|
|
@@ -79,12 +79,12 @@ export function serializeCellsToClipboard(entries: CellEntry[]): TableCellsClipb
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
|
-
* Extract
|
|
82
|
+
* Extract the raw HTML content from a single block's data.
|
|
83
83
|
*
|
|
84
84
|
* Looks for `data.text` (string), then `data.items` (array of strings),
|
|
85
85
|
* and falls back to an empty string.
|
|
86
86
|
*/
|
|
87
|
-
function
|
|
87
|
+
function extractBlockHtml(block: ClipboardBlockData): string {
|
|
88
88
|
const { data } = block;
|
|
89
89
|
|
|
90
90
|
if (typeof data.text === 'string') {
|
|
@@ -100,6 +100,25 @@ function extractBlockText(block: ClipboardBlockData): string {
|
|
|
100
100
|
return '';
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Strip HTML tags from a string, returning only the visible text content.
|
|
105
|
+
*/
|
|
106
|
+
function stripHtmlTags(html: string): string {
|
|
107
|
+
const div = document.createElement('div');
|
|
108
|
+
|
|
109
|
+
div.innerHTML = html;
|
|
110
|
+
|
|
111
|
+
return div.textContent ?? '';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Extract a plain-text representation from a single block's data.
|
|
116
|
+
* HTML tags are stripped so only visible text remains.
|
|
117
|
+
*/
|
|
118
|
+
function extractBlockPlainText(block: ClipboardBlockData): string {
|
|
119
|
+
return stripHtmlTags(extractBlockHtml(block));
|
|
120
|
+
}
|
|
121
|
+
|
|
103
122
|
/**
|
|
104
123
|
* Build an HTML `<table>` string that carries the clipboard payload in a
|
|
105
124
|
* `data-blok-table-cells` attribute.
|
|
@@ -116,7 +135,7 @@ export function buildClipboardHtml(payload: TableCellsClipboard): string {
|
|
|
116
135
|
.map((row) => {
|
|
117
136
|
const cellsHtml = row
|
|
118
137
|
.map((cell) => {
|
|
119
|
-
const text = cell.blocks.map(
|
|
138
|
+
const text = cell.blocks.map(extractBlockHtml).join(' ');
|
|
120
139
|
|
|
121
140
|
const styles = [
|
|
122
141
|
cell.color ? `background-color: ${cell.color}` : '',
|
|
@@ -145,7 +164,7 @@ export function buildClipboardHtml(payload: TableCellsClipboard): string {
|
|
|
145
164
|
export function buildClipboardPlainText(payload: TableCellsClipboard): string {
|
|
146
165
|
return payload.cells
|
|
147
166
|
.map((row) =>
|
|
148
|
-
row.map((cell) => cell.blocks.map(
|
|
167
|
+
row.map((cell) => cell.blocks.map(extractBlockPlainText).join(' ')).join('\t')
|
|
149
168
|
)
|
|
150
169
|
.join('\n');
|
|
151
170
|
}
|
|
@@ -406,7 +425,15 @@ export function parseClipboardHtml(html: string): TableCellsClipboard | null {
|
|
|
406
425
|
return null;
|
|
407
426
|
}
|
|
408
427
|
|
|
409
|
-
|
|
428
|
+
// Browsers re-serialize clipboard HTML, encoding special characters inside
|
|
429
|
+
// attribute values as HTML entities. Decode them back before JSON.parse.
|
|
430
|
+
// Order matters: & must be last to avoid double-decoding (e.g. &lt; → < → <).
|
|
431
|
+
const jsonStr = raw
|
|
432
|
+
.replace(/'/g, "'")
|
|
433
|
+
.replace(/"/g, '"')
|
|
434
|
+
.replace(/</g, '<')
|
|
435
|
+
.replace(/>/g, '>')
|
|
436
|
+
.replace(/&/g, '&');
|
|
410
437
|
|
|
411
438
|
return JSON.parse(jsonStr) as TableCellsClipboard;
|
|
412
439
|
} catch {
|