PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.1.5
Tableberg – Simple Gutenberg Table Block v1.1.5
1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 trunk 0.0.2 0.2.1 0.3.2 0.3.3 0.4.1 0.5.0 0.5.1 0.5.2 0.5.3 0.5.4 0.5.5 0.5.6 0.5.7 All 42 releases
tableberg / src / components / TableElementsInserterExtension / index.tsx

index.tsx in Tableberg – Simple Gutenberg Table Block 1.1.5, at src/components/TableElementsInserterExtension/index.tsx

217 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2 import { createPortal } from "react-dom";
3 import { store as blockEditorStore } from "@wordpress/block-editor";
4 import { useSelect } from "@wordpress/data";
5 import { __ } from "@wordpress/i18n";
6
7 import { CellKey } from "../../attributes";
8 import { createElement } from "../../elements";
9 import { useTableStore } from "../../store";
10 import InserterList from "../cell-inserter/inserter-list";
11 import { getCellInserterItems, InserterItem } from "../cell-inserter/items";
12
13 import "./style.scss";
14
15 function getEditorDocument(): Document {
16 try {
17 const parentDoc = document.defaultView?.parent?.document;
18 return parentDoc || document;
19 } catch {
20 return document;
21 }
22 }
23
24 function findBlocksTabContainer(rootDoc: Document): HTMLElement | null {
25 const sidebar = rootDoc.querySelector(".editor-inserter-sidebar");
26
27 if (!(sidebar instanceof HTMLElement)) {
28 return null;
29 }
30
31 const activeTabPanel = rootDoc.querySelector(
32 ".editor-inserter-sidebar .block-editor-tabbed-sidebar__tabpanel:not([hidden])"
33 );
34
35 if (activeTabPanel instanceof HTMLElement) {
36 const blocksList = activeTabPanel.querySelector(
37 ".block-editor-inserter__block-list"
38 );
39
40 if (blocksList instanceof HTMLElement) {
41 return blocksList;
42 }
43 }
44
45 const blocksList = sidebar.querySelector(
46 ".block-editor-inserter__block-list"
47 );
48
49 return blocksList instanceof HTMLElement ? blocksList : null;
50 }
51
52 export function TableElementsInserterExtension({
53 clientId,
54 }: {
55 clientId: string;
56 }) {
57 const selectedElement = useTableStore(state => state.selectedElement);
58 const selectedCells = useTableStore(state => state.selectedCells);
59 const addElementToCell = useTableStore(state => state.addElementToCell);
60
61 const targetCell = useMemo<CellKey | null>(() => {
62 if (selectedElement) {
63 return selectedElement.cell;
64 }
65
66 return selectedCells[0] || null;
67 }, [selectedElement, selectedCells]);
68
69 const { isTablebergContextSelected } = useSelect(
70 select => {
71 const blockSelect = select(blockEditorStore) as {
72 getSelectedBlockClientId: () => string | null;
73 getBlockParents: (clientId: string) => string[];
74 };
75
76 const selectedBlockClientId =
77 blockSelect.getSelectedBlockClientId();
78 const isCurrentBlockSelected = selectedBlockClientId === clientId;
79 const isCurrentInnerBlockSelected = selectedBlockClientId
80 ? blockSelect
81 .getBlockParents(selectedBlockClientId)
82 .includes(clientId)
83 : false;
84
85 return {
86 isTablebergContextSelected:
87 isCurrentBlockSelected || isCurrentInnerBlockSelected,
88 };
89 },
90 [clientId]
91 );
92
93 const shouldShow = isTablebergContextSelected && targetCell !== null;
94
95 const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);
96 const mountNodeRef = useRef<HTMLElement | null>(null);
97 const cellInserterItems = getCellInserterItems();
98
99 useEffect(() => {
100 if (!shouldShow) {
101 setPortalTarget(null);
102
103 if (mountNodeRef.current?.parentElement) {
104 mountNodeRef.current.parentElement.removeChild(
105 mountNodeRef.current
106 );
107 }
108
109 return;
110 }
111
112 const rootDoc = getEditorDocument();
113 const rootWindow = rootDoc.defaultView;
114
115 const syncTarget = () => {
116 const blocksTabContainer = findBlocksTabContainer(rootDoc);
117
118 if (!blocksTabContainer) {
119 setPortalTarget(null);
120 return;
121 }
122
123 if (!mountNodeRef.current) {
124 mountNodeRef.current = rootDoc.createElement("div");
125 mountNodeRef.current.className =
126 "tableberg-inserter-table-elements-mount";
127 }
128
129 const mountNode = mountNodeRef.current;
130
131 if (mountNode.parentElement !== blocksTabContainer) {
132 blocksTabContainer.prepend(mountNode);
133 } else if (blocksTabContainer.firstElementChild !== mountNode) {
134 blocksTabContainer.prepend(mountNode);
135 }
136
137 setPortalTarget(mountNode);
138 };
139
140 syncTarget();
141
142 let frameId: number | null = null;
143 const observer = new MutationObserver(() => {
144 if (!rootWindow) {
145 syncTarget();
146 return;
147 }
148
149 if (frameId !== null) {
150 rootWindow.cancelAnimationFrame(frameId);
151 }
152
153 frameId = rootWindow.requestAnimationFrame(() => {
154 frameId = null;
155 syncTarget();
156 });
157 });
158
159 observer.observe(rootDoc.body, {
160 childList: true,
161 subtree: true,
162 attributes: true,
163 attributeFilter: ["hidden", "class", "aria-selected"],
164 });
165
166 return () => {
167 observer.disconnect();
168
169 if (frameId !== null && rootWindow) {
170 rootWindow.cancelAnimationFrame(frameId);
171 }
172
173 if (mountNodeRef.current?.parentElement) {
174 mountNodeRef.current.parentElement.removeChild(
175 mountNodeRef.current
176 );
177 }
178 };
179 }, [shouldShow]);
180
181 const onInsertElement = useCallback(
182 (item: InserterItem) => {
183 if (!targetCell) {
184 return;
185 }
186
187 const element = createElement(item.name);
188
189 if (element) {
190 addElementToCell(targetCell, element);
191 }
192 },
193 [addElementToCell, targetCell]
194 );
195
196 if (!shouldShow || !portalTarget) {
197 return null;
198 }
199
200 return createPortal(
201 <div className="tableberg-inserter-table-elements">
202 <div className="block-editor-inserter__panel-header">
203 <h2 className="block-editor-inserter__panel-title">
204 {__("Table elements", "tableberg")}
205 </h2>
206 </div>
207 <div className="block-editor-inserter__panel-content">
208 <InserterList
209 items={cellInserterItems}
210 onSelect={onInsertElement}
211 />
212 </div>
213 </div>,
214 portalTarget
215 );
216 }
217