@alpaca-editor/core 1.0.3812 → 1.0.3815

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 (117) hide show
  1. package/dist/components/ui/context-menu.d.ts +25 -0
  2. package/dist/components/ui/context-menu.js +51 -0
  3. package/dist/components/ui/context-menu.js.map +1 -0
  4. package/dist/config/config.js +3 -2
  5. package/dist/config/config.js.map +1 -1
  6. package/dist/config/types.d.ts +4 -2
  7. package/dist/editor/ComponentInfo.js +1 -1
  8. package/dist/editor/ComponentInfo.js.map +1 -1
  9. package/dist/editor/ConfirmationDialog.d.ts +1 -1
  10. package/dist/editor/ContextMenu.d.ts +1 -1
  11. package/dist/editor/ContextMenu.js +24 -9
  12. package/dist/editor/ContextMenu.js.map +1 -1
  13. package/dist/editor/ImageEditor.js +2 -2
  14. package/dist/editor/ImageEditor.js.map +1 -1
  15. package/dist/editor/ItemInfo.js +2 -2
  16. package/dist/editor/ItemInfo.js.map +1 -1
  17. package/dist/editor/MainLayout.js +3 -3
  18. package/dist/editor/MainLayout.js.map +1 -1
  19. package/dist/editor/Titlebar.js +1 -1
  20. package/dist/editor/Titlebar.js.map +1 -1
  21. package/dist/editor/ai/AiTerminal.js +19 -12
  22. package/dist/editor/ai/AiTerminal.js.map +1 -1
  23. package/dist/editor/client/EditorClient.js +19 -4
  24. package/dist/editor/client/EditorClient.js.map +1 -1
  25. package/dist/editor/client/editContext.d.ts +1 -1
  26. package/dist/editor/client/operations.js +15 -14
  27. package/dist/editor/client/operations.js.map +1 -1
  28. package/dist/editor/client/pageModelBuilder.js +8 -5
  29. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  30. package/dist/editor/commands/componentCommands.js +15 -13
  31. package/dist/editor/commands/componentCommands.js.map +1 -1
  32. package/dist/editor/componentTreeHelper.js +3 -3
  33. package/dist/editor/componentTreeHelper.js.map +1 -1
  34. package/dist/editor/control-center/ControlCenterMenu.js +3 -3
  35. package/dist/editor/control-center/ControlCenterMenu.js.map +1 -1
  36. package/dist/editor/field-types/TreeListEditor.js +4 -4
  37. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  38. package/dist/editor/page-editor-chrome/FrameMenu.js +52 -14
  39. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  40. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +5 -5
  41. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  42. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +114 -45
  43. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  44. package/dist/editor/page-viewer/PageViewerFrame.d.ts +0 -1
  45. package/dist/editor/page-viewer/PageViewerFrame.js +119 -215
  46. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  47. package/dist/editor/page-viewer/pageModelBuilder.d.ts +3 -0
  48. package/dist/editor/page-viewer/pageModelBuilder.js +299 -0
  49. package/dist/editor/page-viewer/pageModelBuilder.js.map +1 -0
  50. package/dist/editor/pageModel.d.ts +5 -0
  51. package/dist/editor/sidebar/ComponentPalette.js +30 -30
  52. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  53. package/dist/editor/sidebar/ComponentTree.js +7 -6
  54. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  55. package/dist/editor/sidebar/MainContentTree.js +1 -1
  56. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  57. package/dist/editor/sidebar/SidebarView.js +3 -3
  58. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  59. package/dist/editor/ui/CopyToClipboardButton.js +2 -1
  60. package/dist/editor/ui/CopyToClipboardButton.js.map +1 -1
  61. package/dist/editor/ui/Icons.d.ts +0 -1
  62. package/dist/editor/ui/Icons.js +0 -3
  63. package/dist/editor/ui/Icons.js.map +1 -1
  64. package/dist/editor/ui/Section.js +1 -1
  65. package/dist/editor/ui/Section.js.map +1 -1
  66. package/dist/editor/ui/SimpleMenu.d.ts +1 -8
  67. package/dist/editor/ui/SimpleMenu.js +1 -1
  68. package/dist/editor/ui/SimpleMenu.js.map +1 -1
  69. package/dist/editor/utils.d.ts +2 -2
  70. package/dist/editor/utils.js +57 -9
  71. package/dist/editor/utils.js.map +1 -1
  72. package/dist/lib/safelist.js +1 -1
  73. package/dist/lib/safelist.js.map +1 -1
  74. package/dist/splash-screen/SplashScreen.js +0 -1
  75. package/dist/splash-screen/SplashScreen.js.map +1 -1
  76. package/dist/styles.css +242 -59
  77. package/dist/types.d.ts +2 -2
  78. package/package.json +3 -2
  79. package/src/components/ui/context-menu.tsx +250 -0
  80. package/src/config/config.tsx +2 -2
  81. package/src/config/types.ts +4 -2
  82. package/src/editor/ComponentInfo.tsx +3 -5
  83. package/src/editor/ConfirmationDialog.tsx +1 -1
  84. package/src/editor/ContextMenu.tsx +68 -19
  85. package/src/editor/ImageEditor.tsx +2 -2
  86. package/src/editor/ItemInfo.tsx +4 -4
  87. package/src/editor/MainLayout.tsx +3 -4
  88. package/src/editor/Titlebar.tsx +1 -1
  89. package/src/editor/ai/AiTerminal.tsx +31 -24
  90. package/src/editor/client/EditorClient.tsx +23 -6
  91. package/src/editor/client/editContext.ts +1 -1
  92. package/src/editor/client/operations.ts +16 -14
  93. package/src/editor/client/pageModelBuilder.ts +26 -18
  94. package/src/editor/commands/componentCommands.tsx +58 -39
  95. package/src/editor/componentTreeHelper.tsx +3 -2
  96. package/src/editor/control-center/ControlCenterMenu.tsx +4 -4
  97. package/src/editor/field-types/TreeListEditor.tsx +11 -11
  98. package/src/editor/page-editor-chrome/FrameMenu.tsx +81 -17
  99. package/src/editor/page-editor-chrome/InlineEditor.tsx +5 -5
  100. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +12 -15
  101. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +159 -68
  102. package/src/editor/page-viewer/PageViewerFrame.tsx +131 -300
  103. package/src/editor/page-viewer/pageModelBuilder.ts +412 -0
  104. package/src/editor/pageModel.ts +5 -0
  105. package/src/editor/sidebar/ComponentPalette.tsx +108 -106
  106. package/src/editor/sidebar/ComponentTree.tsx +10 -5
  107. package/src/editor/sidebar/MainContentTree.tsx +1 -1
  108. package/src/editor/sidebar/SidebarView.tsx +9 -9
  109. package/src/editor/ui/CopyToClipboardButton.tsx +2 -1
  110. package/src/editor/ui/Icons.tsx +0 -18
  111. package/src/editor/ui/Section.tsx +4 -4
  112. package/src/editor/ui/SimpleMenu.tsx +5 -13
  113. package/src/editor/utils.ts +74 -17
  114. package/src/lib/safelist.tsx +2 -0
  115. package/src/splash-screen/SplashScreen.tsx +0 -1
  116. package/src/types.ts +2 -4
  117. package/styles.css +58 -17
@@ -0,0 +1,412 @@
1
+ import { EditContextType } from "../client/editContext";
2
+ import { PageViewContext } from "./pageViewContext";
3
+ import {
4
+ ComponentSkeleton,
5
+ PageSkeleton,
6
+ PlaceholderSkeleton,
7
+ RenderedItemSkeleton,
8
+ } from "../pageModel";
9
+
10
+ export function buildPageModel(
11
+ iframeDocument: Document | undefined,
12
+ editContextRef: React.MutableRefObject<EditContextType | undefined>,
13
+ pageViewContextRef: React.MutableRefObject<PageViewContext | undefined>,
14
+ ) {
15
+ if (!iframeDocument || !editContextRef.current || !pageViewContextRef.current)
16
+ return;
17
+
18
+ const extractItemId = (uri: string): string => {
19
+ const match = uri.match(/\{[A-F0-9-]+\}/i);
20
+ return match
21
+ ? match[0].replace(/[{}]/g, "").toLowerCase()
22
+ : uri.toLowerCase();
23
+ };
24
+
25
+ // Function to ensure IDs are in UUID format
26
+ const ensureUUIDFormat = (id: string): string => {
27
+ // If it's already in UUID format (with or without curly braces), return it
28
+ if (
29
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)
30
+ ) {
31
+ return id;
32
+ }
33
+
34
+ // If it's in curly braces format, remove them
35
+ if (
36
+ /^\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}$/i.test(
37
+ id,
38
+ )
39
+ ) {
40
+ return id.replace(/[{}]/g, "");
41
+ }
42
+
43
+ // If it's a GUID without hyphens, add them
44
+ if (/^[0-9a-f]{32}$/i.test(id)) {
45
+ return `${id.substring(0, 8)}-${id.substring(8, 12)}-${id.substring(12, 16)}-${id.substring(16, 20)}-${id.substring(20, 32)}`;
46
+ }
47
+
48
+ // If it's a shorter ID, pad it to make it a valid UUID
49
+ if (id.length < 32) {
50
+ const paddedId = id.padEnd(32, "0");
51
+ return `${paddedId.substring(0, 8)}-${paddedId.substring(8, 12)}-${paddedId.substring(12, 16)}-${paddedId.substring(16, 20)}-${paddedId.substring(20, 32)}`;
52
+ }
53
+
54
+ // For any other format, return as is
55
+ return id;
56
+ };
57
+
58
+ const pageItemDescriptor = pageViewContextRef.current?.pageItemDescriptor;
59
+
60
+ if (!pageItemDescriptor) return;
61
+
62
+ const pageItem: RenderedItemSkeleton = {
63
+ ...pageItemDescriptor,
64
+ renderedFieldIds: [],
65
+ };
66
+
67
+ const start = performance.now();
68
+
69
+ const treeWalker = iframeDocument.createTreeWalker(
70
+ iframeDocument.documentElement, // Root node to start traversal
71
+ NodeFilter.SHOW_ELEMENT, // Only show element nodes
72
+ null, // Optional filter function, can be `null`
73
+ );
74
+
75
+ const root: ComponentSkeleton = {
76
+ placeholders: [],
77
+ id: "page",
78
+ type: "Page",
79
+ typeId: "",
80
+ name: "Page",
81
+ items: [pageItem],
82
+ datasourceItem: pageItem,
83
+ renderedDictionaryKeys: [],
84
+ editorFields: {},
85
+ };
86
+
87
+ let currentComponent: ComponentSkeleton | undefined = root;
88
+ let currentPlaceholder: undefined | PlaceholderSkeleton;
89
+
90
+ const startComponent = (
91
+ componentStartId: string,
92
+ element: Element,
93
+ dataSourceItemId?: string,
94
+ ) => {
95
+ if (!currentPlaceholder) {
96
+ if (!currentComponent) {
97
+ console.error(
98
+ "Component start without parent component:",
99
+ componentStartId,
100
+ );
101
+ return;
102
+ }
103
+
104
+ currentPlaceholder = currentComponent.placeholders.find(
105
+ (x) => x.key === "implicit_" + currentComponent?.datasourceItem?.id,
106
+ );
107
+
108
+ if (!currentPlaceholder) {
109
+ currentPlaceholder = {
110
+ key: "implicit_" + currentComponent?.datasourceItem?.id,
111
+ name: "Implicit placeholder: " + currentComponent?.datasourceItem?.id,
112
+ description: "",
113
+ components: [],
114
+ parentComponent: currentComponent,
115
+ };
116
+
117
+ if (!currentComponent) {
118
+ console.error(
119
+ "Component start without placeholder or parent component",
120
+ );
121
+ } else {
122
+ currentComponent.placeholders.push(currentPlaceholder);
123
+ }
124
+ }
125
+ }
126
+ if (currentPlaceholder) {
127
+ const language =
128
+ element.getAttribute("data-language") || pageItem.language;
129
+ const version = element.hasAttribute("data-version")
130
+ ? parseInt(element.getAttribute("data-version")!)
131
+ : pageItem.version;
132
+
133
+ const itemId = dataSourceItemId || element.getAttribute("data-itemid");
134
+
135
+ const datasourceItem = itemId
136
+ ? {
137
+ id: itemId,
138
+ language,
139
+ version,
140
+ renderedFieldIds: [],
141
+ }
142
+ : undefined;
143
+
144
+ const layoutId = element.getAttribute("data-layoutid");
145
+
146
+ currentComponent = currentPlaceholder.components.find(
147
+ (x) => x.id === componentStartId,
148
+ );
149
+
150
+ if (!currentComponent) {
151
+ currentComponent = {
152
+ id: componentStartId,
153
+ name: "",
154
+ type: element.getAttribute("data-type") || "",
155
+ typeId: element.getAttribute("data-typeid") || "",
156
+ items: datasourceItem ? [datasourceItem] : [],
157
+ placeholders: [],
158
+ parentPlaceholder: currentPlaceholder,
159
+ renderedDictionaryKeys: [],
160
+ datasourceItem,
161
+ layoutId: layoutId || undefined,
162
+ editorFields: {},
163
+ firstDOMElement:
164
+ element.tagName === "SCRIPT"
165
+ ? (element.nextElementSibling ?? element)
166
+ : element,
167
+ };
168
+
169
+ currentPlaceholder.components.push(currentComponent);
170
+ }
171
+ currentPlaceholder = undefined;
172
+ return currentComponent;
173
+ }
174
+ };
175
+
176
+ const endComponent = (componentEndId: string) => {
177
+ if (!currentComponent || currentComponent.id !== componentEndId) {
178
+ console.error(
179
+ "Component end does not match start",
180
+ currentComponent,
181
+ componentEndId,
182
+ );
183
+
184
+ // Placeholder closed implicitly
185
+ if (currentPlaceholder?.parentComponent.id === componentEndId) {
186
+ currentComponent = currentPlaceholder.parentComponent;
187
+ currentPlaceholder = undefined;
188
+ }
189
+ }
190
+ currentPlaceholder = currentComponent?.parentPlaceholder;
191
+
192
+ if (currentPlaceholder?.key.startsWith("implicit_")) {
193
+ currentComponent = currentPlaceholder.parentComponent;
194
+ } else currentComponent = undefined;
195
+ };
196
+
197
+ const componentStack: { component: ComponentSkeleton; element: Element }[] =
198
+ [];
199
+
200
+ while (treeWalker.nextNode()) {
201
+ const node = treeWalker.currentNode;
202
+
203
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
204
+
205
+ const element = node as Element;
206
+
207
+ if (componentStack.length > 0) {
208
+ const component = componentStack[componentStack.length - 1];
209
+ //if element is not a descendant of the component's firstDOMElement, the component from the stack
210
+ if (component && !component.element.contains(element)) {
211
+ componentStack.pop();
212
+ endComponent(component.component.id);
213
+ }
214
+ }
215
+
216
+ if (currentComponent && element.getAttribute("chrometype") === "field") {
217
+ const id = element.getAttribute("id");
218
+
219
+ if (id) {
220
+ //fld_C866BEE59CE340F18529EF72E1EC34A7_427EC0BD3BF245B9880FBDB0054E7925_en_1_f3327de03c4c4fb39dae57c139e6202d_913_edit
221
+ const idComponents = id.split("_");
222
+ if (idComponents.length > 2) {
223
+ let fieldId = idComponents[2]?.toLocaleLowerCase();
224
+ if (!fieldId) continue;
225
+ fieldId = ensureUUIDFormat(fieldId);
226
+
227
+ let itemId = idComponents[1]?.toLocaleLowerCase();
228
+ if (!itemId) continue;
229
+ itemId = ensureUUIDFormat(itemId);
230
+
231
+ let renderedItem =
232
+ itemId == currentComponent.datasourceItem?.id
233
+ ? currentComponent.datasourceItem
234
+ : currentComponent.items.find((x) => x.id === itemId);
235
+
236
+ if (!renderedItem) {
237
+ renderedItem = {
238
+ id: itemId,
239
+ language: currentComponent.datasourceItem?.language || "",
240
+ version: currentComponent.datasourceItem?.version || 0,
241
+ renderedFieldIds: [],
242
+ };
243
+ currentComponent.items.push(renderedItem);
244
+ }
245
+
246
+ if (
247
+ renderedItem &&
248
+ renderedItem.renderedFieldIds.indexOf(fieldId) === -1
249
+ ) {
250
+ renderedItem.renderedFieldIds.push(fieldId);
251
+ }
252
+
253
+ currentComponent.editorFields;
254
+ }
255
+ }
256
+ }
257
+
258
+ if (currentComponent && element.hasAttribute("data-fieldid")) {
259
+ const fieldId = element.getAttribute("data-fieldid")!;
260
+ const formattedFieldId = ensureUUIDFormat(fieldId);
261
+
262
+ const language =
263
+ element.getAttribute("data-language") ||
264
+ currentComponent.datasourceItem?.language ||
265
+ pageItem.language;
266
+ const version = element.hasAttribute("data-version")
267
+ ? parseInt(element.getAttribute("data-version")!)
268
+ : currentComponent.datasourceItem?.version || pageItem.version;
269
+ const itemId =
270
+ element.getAttribute("data-itemid") ||
271
+ currentComponent.datasourceItem?.id;
272
+
273
+ if (!itemId) continue;
274
+
275
+ let renderedItem = currentComponent.items.find(
276
+ (x) =>
277
+ x.id === itemId && x.language === language && x.version === version,
278
+ );
279
+
280
+ if (!renderedItem) {
281
+ renderedItem = {
282
+ id: itemId,
283
+ language,
284
+ version,
285
+ renderedFieldIds: [],
286
+ };
287
+ currentComponent.items.push(renderedItem);
288
+ }
289
+
290
+ if (renderedItem.renderedFieldIds.indexOf(formattedFieldId) === -1) {
291
+ renderedItem.renderedFieldIds.push(formattedFieldId);
292
+ }
293
+ }
294
+
295
+ if (element.tagName == "DIV" && element.hasAttribute("sc_item")) {
296
+ const componentId = extractItemId(element.getAttribute("sc_item")!);
297
+ if (componentId) {
298
+ const component = startComponent(componentId, element, componentId);
299
+
300
+ if (component) {
301
+ component.lastDOMElement = component.firstDOMElement;
302
+ componentStack.push({ component, element });
303
+ }
304
+ }
305
+ }
306
+
307
+ if (element.tagName === "SCRIPT") {
308
+ const placeholderStartId = element.getAttribute("data-placeholder-start");
309
+ const placeholderEndId = element.getAttribute("data-placeholder-end");
310
+ const componentStartId = element.getAttribute("data-component-start");
311
+ const componentEndId = element.getAttribute("data-component-end");
312
+ const dictionaryKeyStart = element.getAttribute(
313
+ "data-dictionary-key-start",
314
+ );
315
+ const editorGroup = element.getAttribute("data-editor-group");
316
+
317
+ if (currentComponent && editorGroup) {
318
+ let group = currentComponent.editorFields[editorGroup];
319
+ if (!group) {
320
+ group = {
321
+ addFields: [],
322
+ removeFields: [],
323
+ };
324
+ currentComponent.editorFields[editorGroup] = group;
325
+ }
326
+ const addFields = element.getAttribute("data-add-fields")?.split(",");
327
+ const removeFields = element
328
+ .getAttribute("data-remove-fields")
329
+ ?.split(",");
330
+
331
+ group.addFields = [...group.addFields, ...(addFields || [])];
332
+ group.removeFields = [...group.removeFields, ...(removeFields || [])];
333
+ }
334
+
335
+ if (dictionaryKeyStart) {
336
+ if (!currentComponent) {
337
+ console.error(
338
+ "Dictionary key without component:",
339
+ dictionaryKeyStart,
340
+ );
341
+ } else {
342
+ currentComponent.renderedDictionaryKeys.push(dictionaryKeyStart);
343
+ }
344
+ }
345
+ if (placeholderStartId) {
346
+ if (!currentComponent) {
347
+ console.error(
348
+ "Placeholder start without component:",
349
+ placeholderStartId,
350
+ "Current placeholder",
351
+ currentPlaceholder,
352
+ );
353
+ } else {
354
+ currentPlaceholder = currentComponent.placeholders.find(
355
+ (x) => x.key === placeholderStartId,
356
+ );
357
+
358
+ if (!currentPlaceholder) {
359
+ currentPlaceholder = {
360
+ key: placeholderStartId,
361
+ name:
362
+ placeholderStartId.indexOf(currentComponent.id + "_") === 0
363
+ ? placeholderStartId.substring(currentComponent.id.length + 1)
364
+ : placeholderStartId,
365
+ description: "",
366
+ components: [],
367
+ parentComponent: currentComponent,
368
+ };
369
+
370
+ currentComponent.placeholders.push(currentPlaceholder);
371
+ }
372
+ currentComponent = undefined;
373
+ }
374
+ }
375
+
376
+ if (placeholderEndId) {
377
+ currentComponent = currentPlaceholder?.parentComponent;
378
+ if (currentPlaceholder?.key !== placeholderEndId) {
379
+ console.error(
380
+ "Placeholder end does not match start",
381
+ currentPlaceholder,
382
+ placeholderEndId,
383
+ );
384
+ }
385
+ currentPlaceholder = undefined;
386
+ }
387
+
388
+ if (componentStartId) {
389
+ startComponent(componentStartId, element);
390
+ }
391
+
392
+ if (componentEndId) {
393
+ if (currentComponent) {
394
+ currentComponent.lastDOMElement =
395
+ element.previousElementSibling || element;
396
+ endComponent(componentEndId);
397
+ }
398
+ }
399
+ }
400
+ }
401
+
402
+ const page: PageSkeleton = {
403
+ rootComponent: root,
404
+ item: pageItem,
405
+ editRevision: editContextRef.current?.revision ?? "",
406
+ };
407
+
408
+ const time = performance.now() - start;
409
+
410
+ console.log("PAGE MODEL SKELETON", page, time);
411
+ pageViewContextRef.current?.setPageSkeleton(page);
412
+ }
@@ -29,6 +29,8 @@ export type ComponentSkeleton = {
29
29
  renderedDictionaryKeys: string[];
30
30
  layoutId?: string;
31
31
  editorFields: EditorFields;
32
+ firstDOMElement?: Element;
33
+ lastDOMElement?: Element;
32
34
  };
33
35
 
34
36
  export type RenderedItemSkeleton = ItemDescriptor & {
@@ -168,6 +170,9 @@ export type Component = {
168
170
  rendering?: RenderingReference;
169
171
  layoutId?: string;
170
172
  editorFields: EditorFields;
173
+ firstDOMElement?: Element;
174
+ lastDOMElement?: Element;
175
+ canBeMoved: boolean;
171
176
  };
172
177
 
173
178
  export type Placeholder = {
@@ -25,121 +25,123 @@ export function ComponentPalette({}: {}) {
25
25
  acc[group].push(value);
26
26
  return acc;
27
27
  },
28
- {} as { [key: string]: InsertOption[] }
28
+ {} as { [key: string]: InsertOption[] },
29
29
  );
30
30
 
31
31
  return (
32
- <div className="h-full relative tour-component-palette">
33
- <div className="overflow-y-auto absolute inset-0">
34
- <div className="p-4">
35
- <InputText
36
- ref={filterRef}
37
- className="w-full text-sm"
38
- placeholder="Filter components"
39
- onChange={(e) => setFilter(e.target.value)}
40
- />
41
- </div>
42
- {Object.keys(insertOptionsByGroup)
43
- .sort()
44
- .map((group, index) => {
45
- const options = insertOptionsByGroup[group]!.filter(
46
- (x) => !x.isHidden
47
- ).filter(
48
- (x) => x.name.toLowerCase().indexOf(filter.toLowerCase()) > -1
49
- );
50
- if (options.length === 0) return;
51
- return (
52
- <div
53
- className={classNames(
54
- "m-4",
55
- index > 0 ? " border-t border-gray-200" : ""
56
- )}
57
- key={group}
58
- >
59
- <div className="text-xs font-bold mt-2 mb-3 flex items-center gap-2">
60
- <ArrowDownIcon /> {group}
61
- </div>
62
- <div className="grid gap-4 grid-cols-[repeat(auto-fill,_minmax(70px,_1fr))]">
63
- {options.map((option) => {
64
- function handleDragStart(
65
- event: React.DragEvent<HTMLDivElement>
66
- ): void {
67
- editContext?.dragStart({
68
- type: "template",
69
- typeId: option.typeId,
70
- name: option.name,
71
- });
72
- event.stopPropagation();
73
- }
32
+ <div className="tour-component-palette relative flex h-full flex-col">
33
+ <div className="p-4">
34
+ <InputText
35
+ ref={filterRef}
36
+ className="w-full text-sm"
37
+ placeholder="Filter components"
38
+ onChange={(e) => setFilter(e.target.value)}
39
+ />
40
+ </div>
41
+ <div className="relative flex-1">
42
+ <div className="absolute inset-0 overflow-y-auto">
43
+ {Object.keys(insertOptionsByGroup)
44
+ .sort()
45
+ .map((group, index) => {
46
+ const options = insertOptionsByGroup[group]!.filter(
47
+ (x) => !x.isHidden,
48
+ ).filter(
49
+ (x) => x.name.toLowerCase().indexOf(filter.toLowerCase()) > -1,
50
+ );
51
+ if (options.length === 0) return;
52
+ return (
53
+ <div
54
+ className={classNames(
55
+ "m-4",
56
+ index > 0 ? "border-t border-gray-200" : "",
57
+ )}
58
+ key={group}
59
+ >
60
+ <div className="mt-2 mb-3 flex items-center gap-2 text-xs font-bold">
61
+ <ArrowDownIcon /> {group}
62
+ </div>
63
+ <div className="grid grid-cols-[repeat(auto-fill,_minmax(70px,_1fr))] gap-4">
64
+ {options.map((option) => {
65
+ function handleDragStart(
66
+ event: React.DragEvent<HTMLDivElement>,
67
+ ): void {
68
+ editContext?.dragStart({
69
+ type: "template",
70
+ typeId: option.typeId,
71
+ name: option.name,
72
+ });
73
+ event.stopPropagation();
74
+ }
74
75
 
75
- function handleDragEnd(): void {
76
- editContext!.dragEnd();
77
- editContext!.setSelectedForInsertion("");
78
- }
76
+ function handleDragEnd(): void {
77
+ editContext!.dragEnd();
78
+ editContext!.setSelectedForInsertion("");
79
+ }
79
80
 
80
- const isDraggable = !option.isInvalid;
81
+ const isDraggable = !option.isInvalid;
81
82
 
82
- return (
83
- <div
84
- id={"insert-component-" + option.typeId}
85
- onClick={() => {
86
- editContext!.setSelectedForInsertion(option.typeId);
87
- }}
88
- draggable={isDraggable}
89
- onDragStart={handleDragStart}
90
- onDragEnd={handleDragEnd}
91
- className={classNames(
92
- "p-2 border border-gray-200 flex items-center flex-col gap-2 text-center text-xs relative rounded break-all ",
93
- isDraggable ? "cursor-grab" : "",
94
- editContext.selectedForInsertion === option.typeId
95
- ? "border-sky-500"
96
- : ""
97
- )}
98
- key={option.typeId}
99
- data-testid="insert-component-option"
100
- >
101
- {option.svg && (
102
- <div
103
- className="w-8 h-8"
104
- dangerouslySetInnerHTML={{ __html: option.svg }}
105
- ></div>
106
- )}
107
- {!option.svg && (
108
- <img
109
- src={getAbsoluteIconUrl(option.icon)}
110
- width="32"
111
- height="32"
112
- ></img>
113
- )}
83
+ return (
84
+ <div
85
+ id={"insert-component-" + option.typeId}
86
+ onClick={() => {
87
+ editContext!.setSelectedForInsertion(option.typeId);
88
+ }}
89
+ draggable={isDraggable}
90
+ onDragStart={handleDragStart}
91
+ onDragEnd={handleDragEnd}
92
+ className={classNames(
93
+ "relative flex flex-col items-center gap-2 rounded border border-gray-200 p-2 text-center text-xs break-all",
94
+ isDraggable ? "cursor-grab" : "",
95
+ editContext.selectedForInsertion === option.typeId
96
+ ? "border-sky-500"
97
+ : "",
98
+ )}
99
+ key={option.typeId}
100
+ data-testid="insert-component-option"
101
+ >
102
+ {option.svg && (
103
+ <div
104
+ className="h-8 w-8"
105
+ dangerouslySetInnerHTML={{ __html: option.svg }}
106
+ ></div>
107
+ )}
108
+ {!option.svg && (
109
+ <img
110
+ src={getAbsoluteIconUrl(option.icon)}
111
+ width="32"
112
+ height="32"
113
+ ></img>
114
+ )}
114
115
 
115
- <div className="text-xs">
116
- {option.name
117
- .split(new RegExp(`(${filter})`, "i"))
118
- .map((part: string, i: number) =>
119
- part.toLowerCase() === filter.toLowerCase() &&
120
- part ? (
121
- <u className="font-bold" key={i}>
122
- {part}
123
- </u>
124
- ) : (
125
- part && <span key={i}>{part}</span>
126
- )
127
- )}
128
- </div>
116
+ <div className="text-xs">
117
+ {option.name
118
+ .split(new RegExp(`(${filter})`, "i"))
119
+ .map((part: string, i: number) =>
120
+ part.toLowerCase() === filter.toLowerCase() &&
121
+ part ? (
122
+ <u className="font-bold" key={i}>
123
+ {part}
124
+ </u>
125
+ ) : (
126
+ part && <span key={i}>{part}</span>
127
+ ),
128
+ )}
129
+ </div>
129
130
 
130
- {option.isInvalid && (
131
- <i
132
- className="pi pi-exclamation-triangle text-red-500 absolute top-0 right-0"
133
- title={option.message}
134
- />
135
- )}
136
- </div>
137
- );
138
- })}
131
+ {option.isInvalid && (
132
+ <i
133
+ className="pi pi-exclamation-triangle absolute top-0 right-0 text-red-500"
134
+ title={option.message}
135
+ />
136
+ )}
137
+ </div>
138
+ );
139
+ })}
140
+ </div>
139
141
  </div>
140
- </div>
141
- );
142
- })}
142
+ );
143
+ })}
144
+ </div>
143
145
  </div>
144
146
  </div>
145
147
  );