@developer_tribe/react-builder 1.0.2 → 1.0.4

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.
Files changed (147) hide show
  1. package/dist/AttributesEditor.d.ts +3 -1
  2. package/dist/RenderPage.d.ts +2 -1
  3. package/dist/android.svg +43 -0
  4. package/dist/apple.svg +16 -0
  5. package/dist/attributes-editor/Field.d.ts +4 -2
  6. package/dist/attributes-editor/SizeField.d.ts +9 -0
  7. package/dist/attributes-editor/SpecialCategorySection.d.ts +2 -1
  8. package/dist/build-components/BackgroundImage/BackgroundImage.d.ts +5 -0
  9. package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +45 -0
  10. package/dist/build-components/Button/ButtonProps.generated.d.ts +8 -0
  11. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +8 -0
  12. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +8 -0
  13. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +8 -0
  14. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +8 -0
  15. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +8 -0
  16. package/dist/build-components/Image/ImageProps.generated.d.ts +8 -0
  17. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +8 -0
  18. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +8 -1
  19. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +8 -0
  20. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +9 -3
  21. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +8 -0
  22. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +9 -1
  23. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +8 -0
  24. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +8 -1
  25. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +8 -0
  26. package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +8 -0
  27. package/dist/build-components/Text/TextProps.generated.d.ts +8 -0
  28. package/dist/build-components/View/ViewProps.generated.d.ts +8 -0
  29. package/dist/build-components/index.d.ts +2 -1
  30. package/dist/build-components/patterns.generated.d.ts +1612 -46
  31. package/dist/components/AttributesEditorPanel.d.ts +3 -4
  32. package/dist/components/Builder.d.ts +2 -1
  33. package/dist/components/BuilderButton.d.ts +9 -0
  34. package/dist/components/JsonTextEditor.d.ts +9 -0
  35. package/dist/index.cjs.js +5 -5
  36. package/dist/index.cjs.js.map +1 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.esm.js +5 -5
  39. package/dist/index.esm.js.map +1 -1
  40. package/dist/modals/ColorModal.d.ts +3 -1
  41. package/dist/pages/ProjectPage.d.ts +3 -3
  42. package/dist/pages/tabs/BuilderPanel.d.ts +8 -0
  43. package/dist/pages/tabs/SideTool.d.ts +8 -0
  44. package/dist/store.d.ts +9 -1
  45. package/dist/styles.css +1 -1
  46. package/dist/types/Project.d.ts +11 -0
  47. package/dist/utils/analyseNode.d.ts +1 -0
  48. package/dist/utils/extractImageStyle.d.ts +2 -1
  49. package/dist/utils/extractTextStyle.d.ts +8 -1
  50. package/dist/utils/extractViewStyle.d.ts +7 -1
  51. package/dist/utils/parseColor.d.ts +7 -0
  52. package/dist/utils/selection.d.ts +7 -0
  53. package/dist/utils/useMergedStyle.d.ts +2 -0
  54. package/package.json +2 -5
  55. package/src/.DS_Store +0 -0
  56. package/src/AttributesEditor.tsx +83 -16
  57. package/src/RenderPage.tsx +86 -4
  58. package/src/attributes-editor/Field.tsx +60 -165
  59. package/src/attributes-editor/SizeField.tsx +184 -0
  60. package/src/attributes-editor/SpecialCategorySection.tsx +12 -4
  61. package/src/build-components/BackgroundImage/BackgroundImage.tsx +77 -0
  62. package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +61 -0
  63. package/src/build-components/BackgroundImage/pattern.json +45 -0
  64. package/src/build-components/Button/Button.tsx +29 -4
  65. package/src/build-components/Button/ButtonProps.generated.ts +8 -0
  66. package/src/build-components/Carousel/Carousel.tsx +25 -3
  67. package/src/build-components/Carousel/CarouselProps.generated.ts +8 -0
  68. package/src/build-components/CarouselButtons/CarouselButtons.tsx +19 -4
  69. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +8 -0
  70. package/src/build-components/CarouselDots/CarouselDots.tsx +13 -4
  71. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +8 -0
  72. package/src/build-components/CarouselItem/CarouselItem.tsx +20 -4
  73. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +8 -0
  74. package/src/build-components/CarouselProvider/CarouselProvider.tsx +14 -3
  75. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +8 -0
  76. package/src/build-components/Image/Image.tsx +27 -9
  77. package/src/build-components/Image/ImageProps.generated.ts +8 -0
  78. package/src/build-components/Image/pattern.json +1 -9
  79. package/src/build-components/Onboard/Onboard.tsx +2 -2
  80. package/src/build-components/Onboard/OnboardProps.generated.ts +8 -0
  81. package/src/build-components/OnboardButton/OnboardButton.tsx +11 -7
  82. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +8 -1
  83. package/src/build-components/OnboardButtons/OnboardButtons.tsx +17 -5
  84. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +8 -0
  85. package/src/build-components/OnboardDot/OnboardDot.tsx +68 -39
  86. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +9 -3
  87. package/src/build-components/OnboardDot/pattern.json +3 -19
  88. package/src/build-components/OnboardFooter/OnboardFooter.tsx +37 -14
  89. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +8 -0
  90. package/src/build-components/OnboardImage/OnboardImage.tsx +28 -6
  91. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +9 -1
  92. package/src/build-components/OnboardItem/OnboardItem.tsx +15 -14
  93. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +8 -0
  94. package/src/build-components/OnboardProvider/OnboardProvider.tsx +35 -20
  95. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +8 -1
  96. package/src/build-components/OnboardProvider/pattern.json +0 -8
  97. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +8 -0
  98. package/src/build-components/OnboardSubtitle/pattern.json +1 -1
  99. package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +8 -0
  100. package/src/build-components/OnboardTitle/pattern.json +1 -1
  101. package/src/build-components/RenderNode.generated.tsx +3 -0
  102. package/src/build-components/Text/Text.tsx +28 -10
  103. package/src/build-components/Text/TextProps.generated.ts +8 -0
  104. package/src/build-components/View/View.tsx +25 -3
  105. package/src/build-components/View/ViewProps.generated.ts +8 -0
  106. package/src/build-components/View/pattern.json +67 -1
  107. package/src/build-components/index.ts +5 -0
  108. package/src/build-components/patterns.generated.ts +1620 -46
  109. package/src/components/AttributesEditorPanel.tsx +13 -6
  110. package/src/components/Builder.tsx +200 -56
  111. package/src/components/BuilderButton.tsx +127 -0
  112. package/src/components/DeviceNavigationBar.tsx +0 -1
  113. package/src/components/EditorHeader.tsx +11 -1
  114. package/src/components/JsonTextEditor.tsx +185 -0
  115. package/src/index.ts +2 -2
  116. package/src/mockOS/components/MockOSRouter.tsx +17 -3
  117. package/src/mockOS/context/MockOSContext.tsx +0 -5
  118. package/src/mockOS/managers/mockPermissionManager.ts +0 -4
  119. package/src/mockOS/managers/navigationManager.ts +1 -6
  120. package/src/modals/ColorModal.tsx +306 -71
  121. package/src/modals/LocalicationModal.tsx +4 -5
  122. package/src/modals/Modal.tsx +8 -1
  123. package/src/pages/ProjectPage.tsx +299 -55
  124. package/src/pages/tabs/{BuilderTab.tsx → BuilderPanel.tsx} +13 -9
  125. package/src/pages/tabs/SideTool.tsx +260 -0
  126. package/src/size-matters/index.ts +6 -0
  127. package/src/store.ts +18 -1
  128. package/src/styles/base/_global.scss +163 -7
  129. package/src/styles/components/_attributes-editor.scss +12 -0
  130. package/src/styles/components/_editor-shell.scss +25 -0
  131. package/src/styles/foundation/_sizes.scss +1 -1
  132. package/src/styles/layout/_builder.scss +66 -10
  133. package/src/styles/modals/_color-modal.scss +59 -1
  134. package/src/styles/utilities/_carousel.scss +9 -8
  135. package/src/types/Project.ts +14 -0
  136. package/src/utils/analyseNode.ts +98 -0
  137. package/src/utils/extractImageStyle.ts +3 -6
  138. package/src/utils/extractTextStyle.ts +19 -82
  139. package/src/utils/extractViewStyle.ts +41 -12
  140. package/src/utils/parseColor.ts +43 -0
  141. package/src/utils/selection.ts +24 -0
  142. package/src/utils/useMergedStyle.ts +16 -0
  143. package/dist/pages/tabs/BuilderTab.d.ts +0 -9
  144. package/dist/pages/tabs/DebugTab.d.ts +0 -7
  145. package/dist/pages/tabs/PreviewTab.d.ts +0 -3
  146. package/src/pages/tabs/DebugTab.tsx +0 -64
  147. package/src/pages/tabs/PreviewTab.tsx +0 -206
@@ -1,35 +1,82 @@
1
- import { useEffect, useState } from 'react';
2
- import { Node, RenderPage, Project } from '..';
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { Node, NodeData, RenderPage, Project, ProjectColors } from '..';
3
3
  import { EditorHeader } from '../components/EditorHeader';
4
4
  import { AttributesEditorPanel } from '../components/AttributesEditorPanel';
5
- import { BuilderTab } from './tabs/BuilderTab';
6
- import { PreviewTab } from './tabs/PreviewTab';
7
- import { DebugTab } from './tabs/DebugTab';
5
+ import { BuilderPanel } from './tabs/BuilderPanel';
6
+ import { SideTool } from './tabs/SideTool';
8
7
  import { AppConfig, defaultAppConfig } from '../types/PreviewConfig';
9
8
  import { useRenderStore } from '../store';
10
9
  import { logger } from '../utils/logger';
11
10
  import { useLogRender } from '../utils/useLogRender';
12
11
  import type { LogLevel } from '../types/Project';
12
+ import { analyseAndProccess } from '../utils/analyseNode';
13
13
  import backgroundImage from '../assets/images/background.jpg';
14
- export type Tab = 'builder' | 'preview';
15
-
16
14
  export type ProjectPageProps = {
17
15
  project: Project;
18
16
  onSaveProject: (project: Project) => void;
19
17
  appConfig?: AppConfig;
20
18
  logLevel?: LogLevel;
19
+ projectColors?: ProjectColors;
21
20
  };
22
21
 
22
+ const MOBILE_BREAKPOINT = 1000;
23
+
23
24
  export function ProjectPage({
24
25
  project,
25
26
  appConfig = defaultAppConfig,
26
27
  onSaveProject,
27
28
  logLevel,
29
+ projectColors,
28
30
  }: ProjectPageProps) {
29
31
  useLogRender('ProjectPage');
30
- const [current, setCurrent] = useState<Node>(project.data);
31
- const [editorData, setEditorData] = useState<Node>(project.data);
32
- const [tab, setTab] = useState<Tab>('builder');
32
+ const resolvedProjectColors = projectColors ?? project.projectColors;
33
+ const { current, setCurrent, setProjectColors } = useRenderStore((s) => ({
34
+ current: s.current,
35
+ setCurrent: s.setCurrent,
36
+ setProjectColors: s.setProjectColors,
37
+ }));
38
+ const [editorData, setEditorData] = useState<Node>(null);
39
+ const [mobilePanel, setMobilePanel] = useState<
40
+ 'builder' | 'attributes' | null
41
+ >(null);
42
+ const [isMobile, setIsMobile] = useState<boolean>(() => {
43
+ if (typeof window === 'undefined') return false;
44
+ return window.innerWidth <= MOBILE_BREAKPOINT;
45
+ });
46
+
47
+ const handleDeleteNode = useCallback(
48
+ (nodeToDelete: Node) => {
49
+ // Extra warning for deleting the root node (editorData)
50
+ if (nodeToDelete === editorData) {
51
+ const shouldDeleteRoot = window.confirm(
52
+ 'You are about to delete the root component. This will clear the entire screen. Continue?',
53
+ );
54
+ if (!shouldDeleteRoot) return;
55
+ setEditorData(null);
56
+ setCurrent(null);
57
+ return;
58
+ }
59
+ const updated: Node = deleteNodeFromTree(editorData, nodeToDelete);
60
+ //@ts-ignore
61
+ setEditorData(updated);
62
+
63
+ if (current === nodeToDelete) {
64
+ setCurrent(updated);
65
+ return;
66
+ }
67
+
68
+ if (isNodeRecord(current) && nodeHasChild(current, nodeToDelete)) {
69
+ const currentKey = current.key;
70
+ if (currentKey) {
71
+ const nextCurrent = findNodeByKey(updated, currentKey);
72
+ setCurrent(nextCurrent ?? updated);
73
+ } else {
74
+ setCurrent(updated);
75
+ }
76
+ }
77
+ },
78
+ [editorData, current],
79
+ );
33
80
 
34
81
  useEffect(() => {
35
82
  logger.info('ProjectPage', 'mount', { projectName: project.name });
@@ -40,11 +87,59 @@ export function ProjectPage({
40
87
  };
41
88
  }, [appConfig, project.name]);
42
89
 
90
+ useEffect(() => {
91
+ setProjectColors(resolvedProjectColors);
92
+ return () => setProjectColors(undefined);
93
+ }, [resolvedProjectColors, setProjectColors]);
94
+
43
95
  useEffect(() => {
44
96
  if (!logLevel) return;
45
97
  logger.setLevel(logLevel);
46
98
  }, [logLevel]);
47
99
 
100
+ useEffect(() => {
101
+ function handleResize() {
102
+ setIsMobile(window.innerWidth <= MOBILE_BREAKPOINT);
103
+ }
104
+
105
+ handleResize();
106
+ window.addEventListener('resize', handleResize);
107
+ return () => window.removeEventListener('resize', handleResize);
108
+ }, []);
109
+
110
+ useEffect(() => {
111
+ setMobilePanel(null);
112
+ }, [isMobile]);
113
+
114
+ const toggleMobilePanel = (panel: 'builder' | 'attributes') => {
115
+ setMobilePanel((prev) => (prev === panel ? null : panel));
116
+ };
117
+
118
+ const closeMobilePanels = () => {
119
+ setMobilePanel(null);
120
+ };
121
+
122
+ const leftPanelIsOpen = !isMobile || mobilePanel === 'builder';
123
+ const attributesPanelIsOpen = !isMobile || mobilePanel === 'attributes';
124
+
125
+ useEffect(() => {
126
+ try {
127
+ const processed = analyseAndProccess(project.data);
128
+ if (!processed) {
129
+ setEditorData(null);
130
+ setCurrent(null);
131
+ return;
132
+ }
133
+ setEditorData(processed);
134
+ setCurrent(processed);
135
+ } catch (error) {
136
+ // eslint-disable-next-line no-alert
137
+ alert('Duplicate node key detected. Please check your project data.');
138
+ setEditorData(null);
139
+ setCurrent(null);
140
+ }
141
+ }, [project.data]);
142
+
48
143
  return (
49
144
  <div className="container-full">
50
145
  <EditorHeader
@@ -64,53 +159,83 @@ export function ProjectPage({
64
159
  editorData={editorData}
65
160
  setEditorData={setEditorData}
66
161
  />
162
+ {isMobile && (
163
+ <div
164
+ className="mobile-panel-toggle"
165
+ role="group"
166
+ aria-label="Editor panels"
167
+ >
168
+ <button
169
+ type="button"
170
+ className={`mobile-panel-toggle__button${mobilePanel === 'builder' ? ' mobile-panel-toggle__button--active' : ''}`}
171
+ aria-label="Toggle builder panel"
172
+ aria-expanded={mobilePanel === 'builder'}
173
+ aria-controls="split-left-panel"
174
+ onClick={() => toggleMobilePanel('builder')}
175
+ >
176
+ <span className="mobile-panel-toggle__icon" aria-hidden="true">
177
+ <svg viewBox="0 0 16 12" role="presentation" focusable="false">
178
+ <path
179
+ d="M1 1h14M1 6h14M1 11h14"
180
+ stroke="currentColor"
181
+ strokeWidth="2"
182
+ strokeLinecap="round"
183
+ fill="none"
184
+ />
185
+ </svg>
186
+ </span>
187
+ <span className="mobile-panel-toggle__label">Builder</span>
188
+ </button>
189
+ <button
190
+ type="button"
191
+ className={`mobile-panel-toggle__button${mobilePanel === 'attributes' ? ' mobile-panel-toggle__button--active' : ''}`}
192
+ aria-label="Toggle attributes panel"
193
+ aria-expanded={mobilePanel === 'attributes'}
194
+ aria-controls="split-attributes-panel"
195
+ onClick={() => toggleMobilePanel('attributes')}
196
+ >
197
+ <span className="mobile-panel-toggle__icon" aria-hidden="true">
198
+ <svg viewBox="0 0 16 12" role="presentation" focusable="false">
199
+ <path
200
+ d="M1 1h14M1 6h14M1 11h14"
201
+ stroke="currentColor"
202
+ strokeWidth="2"
203
+ strokeLinecap="round"
204
+ fill="none"
205
+ />
206
+ </svg>
207
+ </span>
208
+ <span className="mobile-panel-toggle__label">Attributes</span>
209
+ </button>
210
+ </div>
211
+ )}
67
212
  <div className="editor-container">
68
- <div className="split-left">
69
- <div>
70
- <div
71
- className="editor-tabs"
72
- role="tablist"
73
- aria-label="Editor tabs"
213
+ <div
214
+ id="split-left-panel"
215
+ className={`split-left${leftPanelIsOpen ? ' is-open' : ''}`}
216
+ aria-hidden={isMobile && !leftPanelIsOpen}
217
+ >
218
+ {isMobile && (
219
+ <button
220
+ type="button"
221
+ className="split-panel__close"
222
+ aria-label="Close builder panel"
223
+ onClick={closeMobilePanels}
74
224
  >
75
- <button
76
- className={`editor-tab ${tab === 'builder' ? 'editor-tab--active' : ''}`}
77
- role="tab"
78
- aria-selected={tab === 'builder'}
79
- onClick={() => {
80
- setTab('builder');
81
- logger.info('ProjectPage', 'tab change', { to: 'builder' });
82
- }}
83
- >
84
- <span className="editor-tab__label">Builder</span>
85
- </button>
86
- <button
87
- className={`editor-tab ${tab === 'preview' ? 'editor-tab--active' : ''}`}
88
- role="tab"
89
- aria-selected={tab === 'preview'}
90
- onClick={() => {
91
- setTab('preview');
92
- logger.info('ProjectPage', 'tab change', { to: 'preview' });
93
- }}
94
- >
95
- <span className="editor-tab__label">Preview Config</span>
96
- </button>
97
- </div>
98
-
99
- {tab === 'builder' && (
100
- <BuilderTab
101
- data={editorData}
102
- setData={setEditorData}
103
- current={current}
104
- setCurrent={setCurrent}
105
- />
106
- )}
107
-
108
- {tab === 'preview' && <PreviewTab />}
225
+ Close
226
+ </button>
227
+ )}
228
+ <div>
229
+ <BuilderPanel
230
+ data={editorData}
231
+ setData={setEditorData}
232
+ onDeleteNode={handleDeleteNode}
233
+ />
109
234
  </div>
110
235
  </div>
111
236
  <div className="split-right">
112
237
  <div className="split-right__controls">
113
- <DebugTab data={editorData} setData={setEditorData} />
238
+ <SideTool data={editorData} setData={setEditorData} />
114
239
  </div>
115
240
  <div
116
241
  className="split-right-background"
@@ -118,12 +243,26 @@ export function ProjectPage({
118
243
  backgroundImage: `url(${backgroundImage})`,
119
244
  }}
120
245
  />
121
- <RenderPage data={editorData} name={project.name} />
246
+ {editorData && <RenderPage data={editorData} name={project.name} />}
122
247
  </div>
123
- <div className="split-third">
248
+ <div
249
+ id="split-attributes-panel"
250
+ className={`split-third${attributesPanelIsOpen ? ' is-open' : ''}`}
251
+ aria-hidden={isMobile && !attributesPanelIsOpen}
252
+ >
253
+ {isMobile && (
254
+ <button
255
+ type="button"
256
+ className="split-panel__close"
257
+ aria-label="Close attributes panel"
258
+ onClick={closeMobilePanels}
259
+ >
260
+ Close
261
+ </button>
262
+ )}
124
263
  <AttributesEditorPanel
125
- current={current}
126
264
  attributes={editorData}
265
+ projectColors={resolvedProjectColors}
127
266
  onChange={(data) => {
128
267
  setEditorData(data);
129
268
  let nodeKey: string | undefined = undefined;
@@ -141,10 +280,115 @@ export function ProjectPage({
141
280
  nodeKey ? { nodeKey } : undefined,
142
281
  );
143
282
  }}
144
- setCurrent={setCurrent}
145
283
  />
146
284
  </div>
285
+ {isMobile && mobilePanel && (
286
+ <button
287
+ type="button"
288
+ className="editor-container__overlay"
289
+ aria-label="Close active panel"
290
+ onClick={closeMobilePanels}
291
+ />
292
+ )}
147
293
  </div>
148
294
  </div>
149
295
  );
150
296
  }
297
+
298
+ function deleteNodeFromTree(root: Node, target: Node): Node {
299
+ if (root === null || root === undefined) return root;
300
+ if (typeof root === 'string') return root;
301
+
302
+ if (Array.isArray(root)) {
303
+ let changed = false;
304
+ const nextChildren: Node[] = [];
305
+ for (const child of root) {
306
+ if (child === target) {
307
+ changed = true;
308
+ continue;
309
+ }
310
+ const nextChild = deleteNodeFromTree(child, target);
311
+ if (nextChild !== child) changed = true;
312
+ nextChildren.push(nextChild);
313
+ }
314
+ return changed ? nextChildren : root;
315
+ }
316
+
317
+ const data = root as any;
318
+ if ('children' in data) {
319
+ const prev = data.children as Node;
320
+ if (!prev) return root;
321
+
322
+ if (Array.isArray(prev)) {
323
+ let changed = false;
324
+ const nextChildren: Node[] = [];
325
+ for (const child of prev) {
326
+ if (child === target) {
327
+ changed = true;
328
+ continue;
329
+ }
330
+ const nextChild = deleteNodeFromTree(child, target);
331
+ if (nextChild !== child) changed = true;
332
+ nextChildren.push(nextChild);
333
+ }
334
+ if (changed) {
335
+ return { ...data, children: nextChildren } as Node;
336
+ }
337
+ return root;
338
+ }
339
+
340
+ if (prev === target) {
341
+ return { ...data, children: '' } as Node;
342
+ }
343
+
344
+ const nextChild = deleteNodeFromTree(prev, target);
345
+ if (nextChild !== prev) {
346
+ return { ...data, children: nextChild } as Node;
347
+ }
348
+ }
349
+
350
+ return root;
351
+ }
352
+
353
+ function isNodeRecord(node: Node): node is NodeData {
354
+ return (
355
+ node !== null &&
356
+ node !== undefined &&
357
+ typeof node === 'object' &&
358
+ !Array.isArray(node)
359
+ );
360
+ }
361
+
362
+ function nodeHasChild(parent: NodeData, potentialChild: Node): boolean {
363
+ const { children } = parent;
364
+ if (!children) return false;
365
+ if (Array.isArray(children)) {
366
+ return children.some((child) => child === potentialChild);
367
+ }
368
+ return children === potentialChild;
369
+ }
370
+
371
+ function findNodeByKey(root: Node, key?: string): Node | null {
372
+ if (!key) return null;
373
+ if (root === null || root === undefined) return null;
374
+ if (typeof root === 'string') return null;
375
+
376
+ if (Array.isArray(root)) {
377
+ for (const child of root) {
378
+ const found = findNodeByKey(child, key);
379
+ if (found) return found;
380
+ }
381
+ return null;
382
+ }
383
+
384
+ const nodeData = root as NodeData;
385
+ if (nodeData.key === key) {
386
+ return nodeData;
387
+ }
388
+
389
+ if (nodeData.children) {
390
+ return findNodeByKey(nodeData.children as Node, key);
391
+ }
392
+
393
+ return null;
394
+ }
@@ -1,24 +1,27 @@
1
1
  import { Node } from '../..';
2
2
  import { useLogRender } from '../../utils/useLogRender';
3
3
  import { Builder } from '../../components/Builder';
4
+ import { useRenderStore } from '../../store';
4
5
 
5
- type BuilderTabProps = {
6
+ type BuilderPanelProps = {
6
7
  data: Node;
7
8
  setData: (data: Node) => void;
8
- current: Node;
9
- setCurrent: (current: Node) => void;
9
+ onDeleteNode: (node: Node) => void;
10
10
  };
11
11
 
12
- export function BuilderTab({
12
+ export function BuilderPanel({
13
13
  data,
14
14
  setData,
15
- current,
16
- setCurrent,
17
- }: BuilderTabProps) {
18
- useLogRender('BuilderTab');
15
+ onDeleteNode,
16
+ }: BuilderPanelProps) {
17
+ useLogRender('BuilderPanel');
18
+ const { current, setCurrent } = useRenderStore((s) => ({
19
+ current: s.current,
20
+ setCurrent: s.setCurrent,
21
+ }));
19
22
  return (
20
23
  <div
21
- role="tabpanel"
24
+ role="region"
22
25
  className="editor-panel-builder editor-panel editor-panel--active"
23
26
  aria-hidden={false}
24
27
  >
@@ -27,6 +30,7 @@ export function BuilderTab({
27
30
  setData={setData}
28
31
  current={current}
29
32
  setCurrent={setCurrent}
33
+ onDeleteNode={onDeleteNode}
30
34
  />
31
35
  </div>
32
36
  );