@brixter/brix-builder 0.0.1
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/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/core.d.ts +103 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +758 -0
- package/dist/core.js.map +1 -0
- package/dist/editor/BuilderApp.svelte +1299 -0
- package/dist/editor/BuilderFieldEditor.svelte +274 -0
- package/dist/editor/BuilderInspector.svelte +123 -0
- package/dist/editor/BuilderPreviewFrame.svelte +661 -0
- package/dist/editor/ComponentPreviewThumbnail.svelte +197 -0
- package/dist/editor/PageFlowSidebar.svelte +198 -0
- package/dist/editor/PreviewBlockInserter.svelte +35 -0
- package/dist/editor/PreviewIconEditor.svelte +213 -0
- package/dist/editor/PreviewImageEditor.svelte +221 -0
- package/dist/editor/PreviewTextEditor.svelte +246 -0
- package/dist/editor/RichTextEditor.svelte +234 -0
- package/dist/editor/contracts.d.ts +57 -0
- package/dist/editor/contracts.d.ts.map +1 -0
- package/dist/editor/contracts.js +2 -0
- package/dist/editor/contracts.js.map +1 -0
- package/dist/editor/index.d.ts +3 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +2 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/editor/shortcuts.d.ts +28 -0
- package/dist/editor/shortcuts.d.ts.map +1 -0
- package/dist/editor/shortcuts.js +28 -0
- package/dist/editor/shortcuts.js.map +1 -0
- package/dist/editor-controller.d.ts +50 -0
- package/dist/editor-controller.d.ts.map +1 -0
- package/dist/editor-controller.js +157 -0
- package/dist/editor-controller.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/preview/field-edit-debug.d.ts +5 -0
- package/dist/preview/field-edit-debug.d.ts.map +1 -0
- package/dist/preview/field-edit-debug.js +36 -0
- package/dist/preview/field-edit-debug.js.map +1 -0
- package/dist/preview/interactive-content.d.ts +8 -0
- package/dist/preview/interactive-content.d.ts.map +1 -0
- package/dist/preview/interactive-content.js +62 -0
- package/dist/preview/interactive-content.js.map +1 -0
- package/dist/preview-dom.d.ts +67 -0
- package/dist/preview-dom.d.ts.map +1 -0
- package/dist/preview-dom.js +191 -0
- package/dist/preview-dom.js.map +1 -0
- package/dist/svelte/SveltePreviewRenderer.svelte +490 -0
- package/dist/svelte/adapter.d.ts +7 -0
- package/dist/svelte/adapter.d.ts.map +1 -0
- package/dist/svelte/adapter.js +66 -0
- package/dist/svelte/adapter.js.map +1 -0
- package/dist/svelte/index.d.ts +3 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +3 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/svelte/markup-schema.d.ts +5 -0
- package/dist/svelte/markup-schema.d.ts.map +1 -0
- package/dist/svelte/markup-schema.js +177 -0
- package/dist/svelte/markup-schema.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import { getBuilderDefinition } from '../editor-controller.js';
|
|
4
|
+
import {
|
|
5
|
+
createBuilderFallbackProps,
|
|
6
|
+
normalizeBuilderPropsForRender,
|
|
7
|
+
getFieldByPath,
|
|
8
|
+
inferBuilderFieldKind
|
|
9
|
+
} from '../core.js';
|
|
10
|
+
import PreviewBlockInserter from '../editor/PreviewBlockInserter.svelte';
|
|
11
|
+
import { attachPreviewInteractionGuard } from '../preview/block-preview-interactions.js';
|
|
12
|
+
import type { BuilderAppPreviewProps } from '../editor/contracts.js';
|
|
13
|
+
import type { PreviewOverlay, PreviewCollectionOverlay } from '../preview-dom.js';
|
|
14
|
+
import type { BrikDefinition } from './adapter.js';
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
definitions,
|
|
18
|
+
blocks,
|
|
19
|
+
propsErrors,
|
|
20
|
+
previewOverlays,
|
|
21
|
+
previewCollectionOverlays,
|
|
22
|
+
activeBlockId,
|
|
23
|
+
activeFieldEdit,
|
|
24
|
+
previewContainer,
|
|
25
|
+
onPreviewClick,
|
|
26
|
+
onPreviewKeydown,
|
|
27
|
+
onSelectBlock,
|
|
28
|
+
onDeselectBlock,
|
|
29
|
+
onAddBlockBefore,
|
|
30
|
+
onAddBlockAfter,
|
|
31
|
+
onAddItem,
|
|
32
|
+
onRemoveItem,
|
|
33
|
+
onMoveItem,
|
|
34
|
+
onOpenReorderModal,
|
|
35
|
+
onOpenInserterModal,
|
|
36
|
+
previewMode = false
|
|
37
|
+
}: BuilderAppPreviewProps & { definitions: BrikDefinition[] } = $props();
|
|
38
|
+
|
|
39
|
+
let hoveredCollectionItem = $state<string | null>(null);
|
|
40
|
+
let hoveredCollection = $state<string | null>(null);
|
|
41
|
+
|
|
42
|
+
let blockRenderSnapshotsCache: Record<string, Record<string, unknown>> = {};
|
|
43
|
+
let lastActiveBlockId: string | null = null;
|
|
44
|
+
let lastActivePath: string | null = null;
|
|
45
|
+
|
|
46
|
+
const blockRenderSnapshots = $derived.by(() => {
|
|
47
|
+
const edit = activeFieldEdit;
|
|
48
|
+
if (!edit) {
|
|
49
|
+
blockRenderSnapshotsCache = {};
|
|
50
|
+
lastActiveBlockId = null;
|
|
51
|
+
lastActivePath = null;
|
|
52
|
+
return {} as Record<string, Record<string, unknown>>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (lastActiveBlockId === edit.blockId && lastActivePath === edit.path) {
|
|
56
|
+
return blockRenderSnapshotsCache;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const block = blocks.find((entry) => entry.id === edit.blockId);
|
|
60
|
+
if (!block) {
|
|
61
|
+
return {} as Record<string, Record<string, unknown>>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const definition = getBuilderDefinition(block.type, definitions);
|
|
65
|
+
const field = getFieldByPath(definition.fields, edit.path);
|
|
66
|
+
const kind = field ? inferBuilderFieldKind(field) : null;
|
|
67
|
+
|
|
68
|
+
if (kind === 'image' || kind === 'icon') {
|
|
69
|
+
blockRenderSnapshotsCache = {};
|
|
70
|
+
lastActiveBlockId = edit.blockId;
|
|
71
|
+
lastActivePath = edit.path;
|
|
72
|
+
return {} as Record<string, Record<string, unknown>>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
lastActiveBlockId = edit.blockId;
|
|
76
|
+
lastActivePath = edit.path;
|
|
77
|
+
blockRenderSnapshotsCache = untrack(() => {
|
|
78
|
+
return {
|
|
79
|
+
[edit.blockId]: normalizeBuilderPropsForRender(
|
|
80
|
+
createBuilderFallbackProps(definition, block.props)
|
|
81
|
+
) as Record<string, unknown>
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return blockRenderSnapshotsCache;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function getRenderProps(block: (typeof blocks)[number]): Record<string, unknown> {
|
|
89
|
+
const definition = getBuilderDefinition(block.type, definitions);
|
|
90
|
+
if (previewMode) {
|
|
91
|
+
return normalizeBuilderPropsForRender(block.props) as Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const liveProps = normalizeBuilderPropsForRender(
|
|
95
|
+
createBuilderFallbackProps(definition, block.props)
|
|
96
|
+
) as Record<string, unknown>;
|
|
97
|
+
if (activeFieldEdit?.blockId === block.id && blockRenderSnapshots[block.id]) {
|
|
98
|
+
return blockRenderSnapshots[block.id];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return liveProps;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getCollectionItemKey(blockId: string, collectionPath: string, index: number): string {
|
|
105
|
+
return `${blockId}:${collectionPath}:${index}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function updateHoverStates(
|
|
109
|
+
blockId: string,
|
|
110
|
+
itemOverlays: PreviewOverlay[],
|
|
111
|
+
collectionOverlays: PreviewCollectionOverlay[],
|
|
112
|
+
event: MouseEvent
|
|
113
|
+
): void {
|
|
114
|
+
if (
|
|
115
|
+
isElement(event.target) &&
|
|
116
|
+
event.target.closest('.collection-item-toolbar, .collection-add-button')
|
|
117
|
+
) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const container = event.currentTarget || (isElement(event.target) ? event.target.closest('[data-builder-preview-block]') : null);
|
|
122
|
+
if (!isHTMLElement(container)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 1. Try DOM target matching first (100% accurate, no coordinate issues)
|
|
127
|
+
if (isElement(event.target)) {
|
|
128
|
+
const itemElement = event.target.closest('[data-builder-collection-item]');
|
|
129
|
+
if (itemElement) {
|
|
130
|
+
const collectionPath = itemElement.getAttribute('data-builder-collection-item');
|
|
131
|
+
if (collectionPath) {
|
|
132
|
+
const items = Array.from(container.querySelectorAll(`[data-builder-collection-item="${collectionPath}"]`));
|
|
133
|
+
const index = items.indexOf(itemElement);
|
|
134
|
+
if (index !== -1) {
|
|
135
|
+
hoveredCollectionItem = getCollectionItemKey(blockId, collectionPath, index);
|
|
136
|
+
hoveredCollection = `${blockId}:${collectionPath}`;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 2. Coordinate fallback (in case mouse is over padding or empty space of collection)
|
|
144
|
+
const containerRect = container.getBoundingClientRect();
|
|
145
|
+
const pointerX = event.clientX - containerRect.left;
|
|
146
|
+
const pointerY = event.clientY - containerRect.top;
|
|
147
|
+
|
|
148
|
+
const itemMatch = itemOverlays.find((overlay) => {
|
|
149
|
+
const top = Math.max(0, overlay.top + 36);
|
|
150
|
+
const bottom = top + overlay.height;
|
|
151
|
+
const left = overlay.left;
|
|
152
|
+
const right = left + overlay.width;
|
|
153
|
+
return pointerX >= left && pointerX <= right && pointerY >= top && pointerY <= bottom;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
hoveredCollectionItem = itemMatch
|
|
157
|
+
? getCollectionItemKey(blockId, itemMatch.collectionPath, itemMatch.index)
|
|
158
|
+
: null;
|
|
159
|
+
|
|
160
|
+
const collectionMatch = collectionOverlays.find((overlay) => {
|
|
161
|
+
const top = overlay.top;
|
|
162
|
+
const bottom = top + overlay.height + 45;
|
|
163
|
+
const left = overlay.left;
|
|
164
|
+
const right = left + overlay.width;
|
|
165
|
+
return pointerX >= left && pointerX <= right && pointerY >= top && pointerY <= bottom;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
hoveredCollection = collectionMatch
|
|
169
|
+
? `${blockId}:${collectionMatch.collectionPath}`
|
|
170
|
+
: null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isElement(value: unknown): value is Element {
|
|
174
|
+
return typeof value === 'object' && value !== null && (value as Node).nodeType === 1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isHTMLElement(value: unknown): value is HTMLElement {
|
|
178
|
+
return isElement(value);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getEditingContext(
|
|
182
|
+
blockId: string,
|
|
183
|
+
rawProps: Record<string, unknown>,
|
|
184
|
+
hasPreviewBindings: boolean
|
|
185
|
+
) {
|
|
186
|
+
return {
|
|
187
|
+
active: previewMode ? false : hasPreviewBindings,
|
|
188
|
+
focusPath: activeFieldEdit?.blockId === blockId ? activeFieldEdit.path : null,
|
|
189
|
+
caretOffset:
|
|
190
|
+
activeFieldEdit?.blockId === blockId ? (activeFieldEdit.caretOffset ?? null) : null,
|
|
191
|
+
previewProps: rawProps
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
$effect(() => attachPreviewInteractionGuard(document));
|
|
196
|
+
</script>
|
|
197
|
+
|
|
198
|
+
<div onclick={(event) => {
|
|
199
|
+
if (!(event.target as Element).closest('[data-builder-preview-block]')) {
|
|
200
|
+
onDeselectBlock();
|
|
201
|
+
}
|
|
202
|
+
}}>
|
|
203
|
+
{#each blocks as block, blockIndex (block.id)}
|
|
204
|
+
{@const definition = getBuilderDefinition(block.type, definitions)}
|
|
205
|
+
{#if !propsErrors[block.id]}
|
|
206
|
+
{@const BlockComponent = definition.component}
|
|
207
|
+
{@const renderProps = getRenderProps(block)}
|
|
208
|
+
{@const liveProps = normalizeBuilderPropsForRender(
|
|
209
|
+
createBuilderFallbackProps(definition, block.props)
|
|
210
|
+
) as Record<string, unknown>}
|
|
211
|
+
{@const hasPreviewBindings = definition.previewBindings.length > 0}
|
|
212
|
+
{#if hasPreviewBindings}
|
|
213
|
+
<div
|
|
214
|
+
data-builder-preview-block={block.id}
|
|
215
|
+
use:previewContainer={{
|
|
216
|
+
block,
|
|
217
|
+
definition,
|
|
218
|
+
editing: getEditingContext(block.id, block.props, hasPreviewBindings)
|
|
219
|
+
}}
|
|
220
|
+
class="group relative scroll-mt-0.5 scroll-mb-0.5 transition"
|
|
221
|
+
class:cursor-pointer={!previewMode}
|
|
222
|
+
role={previewMode ? undefined : "button"}
|
|
223
|
+
tabindex={previewMode ? undefined : 0}
|
|
224
|
+
aria-label={previewMode ? undefined : `Modifica elementi del brik ${definition.type}`}
|
|
225
|
+
onclick={previewMode ? undefined : (event: MouseEvent) => onPreviewClick(block, event)}
|
|
226
|
+
onkeydown={previewMode ? undefined : (event: KeyboardEvent) => onPreviewKeydown(block, event)}
|
|
227
|
+
onmousemove={previewMode ? undefined : (event: MouseEvent) =>
|
|
228
|
+
updateHoverStates(
|
|
229
|
+
block.id,
|
|
230
|
+
previewOverlays[block.id] ?? [],
|
|
231
|
+
previewCollectionOverlays[block.id] ?? [],
|
|
232
|
+
event
|
|
233
|
+
)}
|
|
234
|
+
onmouseleave={previewMode ? undefined : () => {
|
|
235
|
+
hoveredCollectionItem = null;
|
|
236
|
+
hoveredCollection = null;
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
{#if !previewMode}
|
|
240
|
+
<PreviewBlockInserter
|
|
241
|
+
placement="before"
|
|
242
|
+
edgeInset={blockIndex === 0}
|
|
243
|
+
onToggle={() => onOpenInserterModal(block.id, 'before')}
|
|
244
|
+
/>
|
|
245
|
+
{/if}
|
|
246
|
+
<div data-builder-preview-content>
|
|
247
|
+
<BlockComponent {...renderProps} />
|
|
248
|
+
</div>
|
|
249
|
+
{#if activeBlockId === block.id && !previewMode}
|
|
250
|
+
<div
|
|
251
|
+
class="pointer-events-none absolute inset-px z-30 border-2 border-[#FDE047] dark:border-[#FACC15]"
|
|
252
|
+
></div>
|
|
253
|
+
{/if}
|
|
254
|
+
|
|
255
|
+
{#if definition.collections.length > 0 && !previewMode}
|
|
256
|
+
<div class="pointer-events-none absolute inset-0">
|
|
257
|
+
{#each previewCollectionOverlays[block.id] ?? [] as overlay (overlay.collectionPath)}
|
|
258
|
+
<div
|
|
259
|
+
class="collection-overlay pointer-events-none absolute z-10"
|
|
260
|
+
style={`top:${overlay.top}px; left:${overlay.left}px; width:${overlay.width}px; height:${overlay.height}px;`}
|
|
261
|
+
>
|
|
262
|
+
<div
|
|
263
|
+
class="collection-outline absolute inset-0 outline outline-1 outline-[#FDE047] transition outline-dashed dark:outline-[#FACC15] {hoveredCollection === `${block.id}:${overlay.collectionPath}` ? 'opacity-100' : 'opacity-0'}"
|
|
264
|
+
></div>
|
|
265
|
+
<button
|
|
266
|
+
type="button"
|
|
267
|
+
class="collection-add-button btn-brutal-icon pointer-events-auto absolute top-full left-1/2 flex h-7 w-7 -translate-x-1/2 translate-y-2 items-center justify-center text-lg leading-none transition {hoveredCollection === `${block.id}:${overlay.collectionPath}` ? 'opacity-100' : 'opacity-0'}"
|
|
268
|
+
aria-label={`Aggiungi ${overlay.label}`}
|
|
269
|
+
onclick={(event) => {
|
|
270
|
+
event.stopPropagation();
|
|
271
|
+
onAddItem(block, overlay.collectionPath);
|
|
272
|
+
}}
|
|
273
|
+
>
|
|
274
|
+
+
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
{/each}
|
|
278
|
+
|
|
279
|
+
{#each previewOverlays[block.id] ?? [] as overlay (`${overlay.collectionPath}-${overlay.index}`)}
|
|
280
|
+
<div
|
|
281
|
+
class="collection-item-overlay pointer-events-none absolute z-20"
|
|
282
|
+
style={`top:${Math.max(0, overlay.top + 36)}px; left:${overlay.left}px; width:${overlay.width}px; height:${overlay.height}px;`}
|
|
283
|
+
>
|
|
284
|
+
<div
|
|
285
|
+
class={hoveredCollectionItem ===
|
|
286
|
+
getCollectionItemKey(block.id, overlay.collectionPath, overlay.index)
|
|
287
|
+
? 'collection-item-outline absolute inset-0 opacity-100 outline outline-1 outline-[#FDE047] transition dark:outline-[#FACC15]'
|
|
288
|
+
: 'collection-item-outline absolute inset-0 opacity-0 outline outline-1 outline-[#FDE047] transition dark:outline-[#FACC15]'}
|
|
289
|
+
></div>
|
|
290
|
+
<div
|
|
291
|
+
class={hoveredCollectionItem ===
|
|
292
|
+
getCollectionItemKey(block.id, overlay.collectionPath, overlay.index)
|
|
293
|
+
? 'collection-item-toolbar pointer-events-auto absolute top-0 left-0 flex h-8 -translate-y-full items-center overflow-hidden border border-gray-300 bg-white text-xs text-gray-900 opacity-100 shadow-[0_2px_8px_rgba(0,0,0,0.18)] transition dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100'
|
|
294
|
+
: 'collection-item-toolbar pointer-events-auto absolute top-0 left-0 flex h-8 -translate-y-full items-center overflow-hidden border border-gray-300 bg-white text-xs text-gray-900 opacity-0 shadow-[0_2px_8px_rgba(0,0,0,0.18)] transition dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100'}
|
|
295
|
+
>
|
|
296
|
+
<button
|
|
297
|
+
type="button"
|
|
298
|
+
class="h-full border-r border-gray-200 px-2.5 transition-colors hover:bg-gray-100 dark:border-gray-700 dark:hover:bg-gray-700"
|
|
299
|
+
onclick={(event) => {
|
|
300
|
+
event.stopPropagation();
|
|
301
|
+
onMoveItem(block, overlay.collectionPath, overlay.index, -1);
|
|
302
|
+
}}
|
|
303
|
+
>
|
|
304
|
+
↑
|
|
305
|
+
</button>
|
|
306
|
+
<button
|
|
307
|
+
type="button"
|
|
308
|
+
class="h-full border-r border-gray-200 px-2.5 transition-colors hover:bg-gray-100 dark:border-gray-700 dark:hover:bg-gray-700"
|
|
309
|
+
onclick={(event) => {
|
|
310
|
+
event.stopPropagation();
|
|
311
|
+
onMoveItem(block, overlay.collectionPath, overlay.index, 1);
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
↓
|
|
315
|
+
</button>
|
|
316
|
+
<button
|
|
317
|
+
type="button"
|
|
318
|
+
class="h-full px-2.5 text-red-600 transition-colors hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950"
|
|
319
|
+
onclick={(event) => {
|
|
320
|
+
event.stopPropagation();
|
|
321
|
+
onRemoveItem(block, overlay.collectionPath, overlay.index);
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
×
|
|
325
|
+
</button>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
{/each}
|
|
329
|
+
</div>
|
|
330
|
+
{/if}
|
|
331
|
+
|
|
332
|
+
{#if !previewMode}
|
|
333
|
+
<PreviewBlockInserter
|
|
334
|
+
placement="after"
|
|
335
|
+
edgeInset={blockIndex === blocks.length - 1}
|
|
336
|
+
onToggle={() => onOpenInserterModal(block.id, 'after')}
|
|
337
|
+
/>
|
|
338
|
+
{/if}
|
|
339
|
+
</div>
|
|
340
|
+
{:else}
|
|
341
|
+
<div
|
|
342
|
+
data-builder-preview-block={block.id}
|
|
343
|
+
use:previewContainer={{
|
|
344
|
+
block,
|
|
345
|
+
definition,
|
|
346
|
+
editing: getEditingContext(block.id, block.props, hasPreviewBindings)
|
|
347
|
+
}}
|
|
348
|
+
class="group relative scroll-mt-0.5 scroll-mb-0.5 transition"
|
|
349
|
+
class:cursor-pointer={!previewMode}
|
|
350
|
+
role={previewMode ? undefined : "button"}
|
|
351
|
+
tabindex={previewMode ? undefined : 0}
|
|
352
|
+
aria-label={previewMode ? undefined : `Seleziona brik ${definition.type}`}
|
|
353
|
+
onclick={previewMode ? undefined : () => onSelectBlock(block.id)}
|
|
354
|
+
onkeydown={previewMode ? undefined : (event: KeyboardEvent) => {
|
|
355
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
356
|
+
event.preventDefault();
|
|
357
|
+
onSelectBlock(block.id);
|
|
358
|
+
}
|
|
359
|
+
}}
|
|
360
|
+
onmousemove={previewMode ? undefined : (event: MouseEvent) =>
|
|
361
|
+
updateHoverStates(
|
|
362
|
+
block.id,
|
|
363
|
+
previewOverlays[block.id] ?? [],
|
|
364
|
+
previewCollectionOverlays[block.id] ?? [],
|
|
365
|
+
event
|
|
366
|
+
)}
|
|
367
|
+
onmouseleave={previewMode ? undefined : () => {
|
|
368
|
+
hoveredCollectionItem = null;
|
|
369
|
+
hoveredCollection = null;
|
|
370
|
+
}}
|
|
371
|
+
>
|
|
372
|
+
{#if !previewMode}
|
|
373
|
+
<PreviewBlockInserter
|
|
374
|
+
placement="before"
|
|
375
|
+
edgeInset={blockIndex === 0}
|
|
376
|
+
onToggle={() => onOpenInserterModal(block.id, 'before')}
|
|
377
|
+
/>
|
|
378
|
+
{/if}
|
|
379
|
+
<div data-builder-preview-content>
|
|
380
|
+
<BlockComponent {...renderProps} />
|
|
381
|
+
</div>
|
|
382
|
+
{#if activeBlockId === block.id && !previewMode}
|
|
383
|
+
<div
|
|
384
|
+
class="pointer-events-none absolute inset-px z-30 border-2 border-[#FDE047] dark:border-[#FACC15]"
|
|
385
|
+
></div>
|
|
386
|
+
{/if}
|
|
387
|
+
|
|
388
|
+
{#if definition.collections.length > 0 && !previewMode}
|
|
389
|
+
<div class="pointer-events-none absolute inset-0">
|
|
390
|
+
{#each previewCollectionOverlays[block.id] ?? [] as overlay (overlay.collectionPath)}
|
|
391
|
+
<div
|
|
392
|
+
class="collection-overlay pointer-events-none absolute z-10"
|
|
393
|
+
style={`top:${overlay.top}px; left:${overlay.left}px; width:${overlay.width}px; height:${overlay.height}px;`}
|
|
394
|
+
>
|
|
395
|
+
<div
|
|
396
|
+
class="collection-outline absolute inset-0 outline outline-1 outline-[#FDE047] transition outline-dashed dark:outline-[#FACC15] {hoveredCollection === `${block.id}:${overlay.collectionPath}` ? 'opacity-100' : 'opacity-0'}"
|
|
397
|
+
></div>
|
|
398
|
+
<button
|
|
399
|
+
type="button"
|
|
400
|
+
class="collection-add-button btn-brutal-icon pointer-events-auto absolute top-full left-1/2 flex h-7 w-7 -translate-x-1/2 translate-y-2 items-center justify-center text-lg leading-none transition {hoveredCollection === `${block.id}:${overlay.collectionPath}` ? 'opacity-100' : 'opacity-0'}"
|
|
401
|
+
aria-label={`Aggiungi ${overlay.label}`}
|
|
402
|
+
onclick={(event) => {
|
|
403
|
+
event.stopPropagation();
|
|
404
|
+
onAddItem(block, overlay.collectionPath);
|
|
405
|
+
}}
|
|
406
|
+
>
|
|
407
|
+
+
|
|
408
|
+
</button>
|
|
409
|
+
</div>
|
|
410
|
+
{/each}
|
|
411
|
+
|
|
412
|
+
{#each previewOverlays[block.id] ?? [] as overlay (`${overlay.collectionPath}-${overlay.index}`)}
|
|
413
|
+
<div
|
|
414
|
+
class="collection-item-overlay pointer-events-none absolute z-20"
|
|
415
|
+
style={`top:${Math.max(0, overlay.top + 36)}px; left:${overlay.left}px; width:${overlay.width}px; height:${overlay.height}px;`}
|
|
416
|
+
>
|
|
417
|
+
<div
|
|
418
|
+
class={hoveredCollectionItem ===
|
|
419
|
+
getCollectionItemKey(block.id, overlay.collectionPath, overlay.index)
|
|
420
|
+
? 'collection-item-outline absolute inset-0 opacity-100 outline outline-1 outline-[#FDE047] transition dark:outline-[#FACC15]'
|
|
421
|
+
: 'collection-item-outline absolute inset-0 opacity-0 outline outline-1 outline-[#FDE047] transition dark:outline-[#FACC15]'}
|
|
422
|
+
></div>
|
|
423
|
+
<div
|
|
424
|
+
class={hoveredCollectionItem ===
|
|
425
|
+
getCollectionItemKey(block.id, overlay.collectionPath, overlay.index)
|
|
426
|
+
? 'collection-item-toolbar pointer-events-auto absolute top-0 left-0 flex h-8 -translate-y-full items-center overflow-hidden border border-gray-300 bg-white text-xs text-gray-900 opacity-100 shadow-[0_2px_8px_rgba(0,0,0,0.18)] transition dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100'
|
|
427
|
+
: 'collection-item-toolbar pointer-events-auto absolute top-0 left-0 flex h-8 -translate-y-full items-center overflow-hidden border border-gray-300 bg-white text-xs text-gray-900 opacity-0 shadow-[0_2px_8px_rgba(0,0,0,0.18)] transition dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100'}
|
|
428
|
+
>
|
|
429
|
+
<button
|
|
430
|
+
type="button"
|
|
431
|
+
class="h-full border-r border-gray-200 px-2.5 transition-colors hover:bg-gray-100 dark:border-gray-700 dark:hover:bg-gray-700"
|
|
432
|
+
onclick={(event) => {
|
|
433
|
+
event.stopPropagation();
|
|
434
|
+
onMoveItem(block, overlay.collectionPath, overlay.index, -1);
|
|
435
|
+
}}
|
|
436
|
+
>
|
|
437
|
+
↑
|
|
438
|
+
</button>
|
|
439
|
+
<button
|
|
440
|
+
type="button"
|
|
441
|
+
class="h-full border-r border-gray-200 px-2.5 transition-colors hover:bg-gray-100 dark:border-gray-700 dark:hover:bg-gray-700"
|
|
442
|
+
onclick={(event) => {
|
|
443
|
+
event.stopPropagation();
|
|
444
|
+
onMoveItem(block, overlay.collectionPath, overlay.index, 1);
|
|
445
|
+
}}
|
|
446
|
+
>
|
|
447
|
+
↓
|
|
448
|
+
</button>
|
|
449
|
+
<button
|
|
450
|
+
type="button"
|
|
451
|
+
class="h-full px-2.5 text-red-600 transition-colors hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950"
|
|
452
|
+
onclick={(event) => {
|
|
453
|
+
event.stopPropagation();
|
|
454
|
+
onRemoveItem(block, overlay.collectionPath, overlay.index);
|
|
455
|
+
}}
|
|
456
|
+
>
|
|
457
|
+
×
|
|
458
|
+
</button>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
{/each}
|
|
462
|
+
</div>
|
|
463
|
+
{/if}
|
|
464
|
+
|
|
465
|
+
{#if !previewMode}
|
|
466
|
+
<PreviewBlockInserter
|
|
467
|
+
placement="after"
|
|
468
|
+
edgeInset={blockIndex === blocks.length - 1}
|
|
469
|
+
onToggle={() => onOpenInserterModal(block.id, 'after')}
|
|
470
|
+
/>
|
|
471
|
+
{/if}
|
|
472
|
+
</div>
|
|
473
|
+
{/if}
|
|
474
|
+
{:else}
|
|
475
|
+
<div
|
|
476
|
+
class="border border-dashed border-red-300 bg-red-50 px-4 py-3 text-sm text-red-600 dark:border-red-800 dark:bg-red-950 dark:text-red-400"
|
|
477
|
+
>
|
|
478
|
+
Correggi i contenuti di questo brik per vedere di nuovo la preview.
|
|
479
|
+
</div>
|
|
480
|
+
{/if}
|
|
481
|
+
{/each}
|
|
482
|
+
</div>
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
<style>
|
|
486
|
+
.collection-overlay:focus-within .collection-outline,
|
|
487
|
+
.collection-overlay:focus-within .collection-add-button {
|
|
488
|
+
opacity: 1;
|
|
489
|
+
}
|
|
490
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
import type { BuilderDefinition } from '../core.js';
|
|
3
|
+
export interface BrikDefinition extends BuilderDefinition {
|
|
4
|
+
component: Component<Record<string, unknown>>;
|
|
5
|
+
}
|
|
6
|
+
export declare function createBrixDefinitions(brikModules: Record<string, unknown>, brikSources?: Record<string, string>): BrikDefinition[];
|
|
7
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../svelte/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAEX,iBAAiB,EAIjB,MAAM,YAAY,CAAC;AAkBpB,MAAM,WAAW,cAAe,SAAQ,iBAAiB;IACxD,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC9C;AAED,wBAAgB,qBAAqB,CACpC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACtC,cAAc,EAAE,CAYlB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createBuilderCollectionsFromFields, createBuilderDefaultsFromFields, createBuilderPreviewBindingsFromFields } from '../core.js';
|
|
2
|
+
import { createBrikSchemaFromMarkup, mergeBuilderFields } from './markup-schema.js';
|
|
3
|
+
export function createBrixDefinitions(brikModules, brikSources = {}) {
|
|
4
|
+
return Object.entries(brikModules)
|
|
5
|
+
.map(([path, module]) => createDefinition(path, module, brikSources[path] ?? ''))
|
|
6
|
+
.sort((a, b) => {
|
|
7
|
+
if (a.mode !== b.mode) {
|
|
8
|
+
return a.mode === 'markdown' ? -1 : 1;
|
|
9
|
+
}
|
|
10
|
+
return a.type.localeCompare(b.type);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function createDefinition(path, module, source) {
|
|
14
|
+
const type = path.split('/').pop()?.replace('.svelte', '') ?? path;
|
|
15
|
+
const markupFields = source ? createBrikSchemaFromMarkup(source) : {};
|
|
16
|
+
const fields = mergeBuilderFields(markupFields, cloneValue(module.brikFields ?? {}));
|
|
17
|
+
const defaults = Object.keys(fields).length > 0
|
|
18
|
+
? mergeDefaults(createBuilderDefaultsFromFields(fields), cloneValue(module.brikDefaults ?? {}))
|
|
19
|
+
: cloneValue(module.brikDefaults ?? {});
|
|
20
|
+
const previewBindings = Object.keys(fields).length > 0
|
|
21
|
+
? createBuilderPreviewBindingsFromFields(fields)
|
|
22
|
+
: cloneValue(module.brikPreviewBindings ?? []);
|
|
23
|
+
const collections = Object.keys(fields).length > 0
|
|
24
|
+
? createBuilderCollectionsFromFields(fields)
|
|
25
|
+
: cloneValue(module.brikCollections ?? []);
|
|
26
|
+
return {
|
|
27
|
+
type,
|
|
28
|
+
path: toLibImportPath(type),
|
|
29
|
+
description: module.brikDescription ?? `Brik ${humanizeType(type)}.`,
|
|
30
|
+
mode: module.brikMode ?? 'component',
|
|
31
|
+
component: module.default,
|
|
32
|
+
defaults,
|
|
33
|
+
previewBindings,
|
|
34
|
+
collections,
|
|
35
|
+
fields
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function toLibImportPath(type) {
|
|
39
|
+
return `$lib/brixter/brix/${type}.svelte`;
|
|
40
|
+
}
|
|
41
|
+
function humanizeType(type) {
|
|
42
|
+
return type
|
|
43
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
44
|
+
.split(/[-_ ]/)
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
47
|
+
.join(' ');
|
|
48
|
+
}
|
|
49
|
+
function cloneValue(value) {
|
|
50
|
+
return JSON.parse(JSON.stringify(value));
|
|
51
|
+
}
|
|
52
|
+
function mergeDefaults(baseDefaults, overrideDefaults) {
|
|
53
|
+
const merged = { ...baseDefaults };
|
|
54
|
+
for (const [key, value] of Object.entries(overrideDefaults)) {
|
|
55
|
+
const baseValue = merged[key];
|
|
56
|
+
merged[key] =
|
|
57
|
+
isRecord(baseValue) && isRecord(value)
|
|
58
|
+
? mergeDefaults(baseValue, value)
|
|
59
|
+
: cloneValue(value);
|
|
60
|
+
}
|
|
61
|
+
return merged;
|
|
62
|
+
}
|
|
63
|
+
function isRecord(value) {
|
|
64
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../svelte/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAuB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BuilderFields } from '../core.js';
|
|
2
|
+
export declare function createBuilderFieldsFromMarkup(source: string): BuilderFields;
|
|
3
|
+
export declare function createBrikSchemaFromMarkup(source: string): BuilderFields;
|
|
4
|
+
export declare function mergeBuilderFields(baseFields: BuilderFields, overrideFields: BuilderFields): BuilderFields;
|
|
5
|
+
//# sourceMappingURL=markup-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markup-schema.d.ts","sourceRoot":"","sources":["../../svelte/markup-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkC,aAAa,EAAE,MAAM,YAAY,CAAC;AAahF,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CA0B3E;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAExE;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,GAAG,aAAa,CAS1G"}
|