vue-wswg-editor 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/README.md +91 -0
- package/dist/style.css +1 -0
- package/dist/types/components/AddBlockItem/AddBlockItem.vue.d.ts +6 -0
- package/dist/types/components/BlockBrowser/BlockBrowser.vue.d.ts +2 -0
- package/dist/types/components/BlockComponent/BlockComponent.vue.d.ts +15 -0
- package/dist/types/components/BlockEditorFieldNode/BlockEditorFieldNode.vue.d.ts +15 -0
- package/dist/types/components/BlockEditorFields/BlockEditorFields.vue.d.ts +15 -0
- package/dist/types/components/BlockMarginFieldNode/BlockMarginNode.vue.d.ts +23 -0
- package/dist/types/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue.d.ts +15 -0
- package/dist/types/components/BrowserNavigation/BrowserNavigation.vue.d.ts +5 -0
- package/dist/types/components/EmptyState/EmptyState.vue.d.ts +15 -0
- package/dist/types/components/PageBlockList/PageBlockList.vue.d.ts +19 -0
- package/dist/types/components/PageBuilderSidebar/PageBuilderSidebar.vue.d.ts +30 -0
- package/dist/types/components/PageBuilderToolbar/PageBuilderToolbar.vue.d.ts +28 -0
- package/dist/types/components/PageRenderer/PageRenderer.vue.d.ts +6 -0
- package/dist/types/components/PageRenderer/blockModules.d.ts +1 -0
- package/dist/types/components/PageSettings/PageSettings.vue.d.ts +15 -0
- package/dist/types/components/ResizeHandle/ResizeHandle.vue.d.ts +6 -0
- package/dist/types/components/WswgJsonEditor/WswgJsonEditor.test.d.ts +1 -0
- package/dist/types/components/WswgJsonEditor/WswgJsonEditor.vue.d.ts +40 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/dist/types/util/fieldConfig.d.ts +82 -0
- package/dist/types/util/helpers.d.ts +28 -0
- package/dist/types/util/registry.d.ts +21 -0
- package/dist/types/util/validation.d.ts +15 -0
- package/dist/vue-wswg-editor.es.js +3377 -0
- package/package.json +85 -0
- package/src/assets/images/empty-state.jpg +0 -0
- package/src/assets/styles/_mixins.scss +73 -0
- package/src/assets/styles/main.css +3 -0
- package/src/components/AddBlockItem/AddBlockItem.vue +50 -0
- package/src/components/BlockBrowser/BlockBrowser.vue +69 -0
- package/src/components/BlockComponent/BlockComponent.vue +186 -0
- package/src/components/BlockEditorFieldNode/BlockEditorFieldNode.vue +378 -0
- package/src/components/BlockEditorFields/BlockEditorFields.vue +91 -0
- package/src/components/BlockMarginFieldNode/BlockMarginNode.vue +132 -0
- package/src/components/BlockRepeaterFieldNode/BlockRepeaterNode.vue +217 -0
- package/src/components/BrowserNavigation/BrowserNavigation.vue +27 -0
- package/src/components/EmptyState/EmptyState.vue +94 -0
- package/src/components/PageBlockList/PageBlockList.vue +103 -0
- package/src/components/PageBuilderSidebar/PageBuilderSidebar.vue +241 -0
- package/src/components/PageBuilderToolbar/PageBuilderToolbar.vue +63 -0
- package/src/components/PageRenderer/PageRenderer.vue +65 -0
- package/src/components/PageRenderer/blockModules-alternative.ts.example +9 -0
- package/src/components/PageRenderer/blockModules-manual.ts.example +19 -0
- package/src/components/PageRenderer/blockModules-runtime.ts.example +23 -0
- package/src/components/PageRenderer/blockModules.ts +3 -0
- package/src/components/PageSettings/PageSettings.vue +86 -0
- package/src/components/ResizeHandle/ResizeHandle.vue +105 -0
- package/src/components/WswgJsonEditor/WswgJsonEditor.test.ts +43 -0
- package/src/components/WswgJsonEditor/WswgJsonEditor.vue +391 -0
- package/src/index.ts +15 -0
- package/src/shims.d.ts +72 -0
- package/src/style.css +3 -0
- package/src/types/Block.d.ts +19 -0
- package/src/types/Layout.d.ts +9 -0
- package/src/util/fieldConfig.ts +173 -0
- package/src/util/helpers.ts +176 -0
- package/src/util/registry.ts +149 -0
- package/src/util/validation.ts +110 -0
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-wswg-editor",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"module": "src/index.ts",
|
|
7
|
+
"typings": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./src/index.ts",
|
|
11
|
+
"require": "./src/index.ts",
|
|
12
|
+
"types": "./src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"./PageRenderer": {
|
|
15
|
+
"import": "./src/components/PageRenderer/PageRenderer.vue",
|
|
16
|
+
"types": "./src/components/PageRenderer/PageRenderer.vue"
|
|
17
|
+
},
|
|
18
|
+
"./style.css": "./dist/style.css"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"dev": "vite build --watch",
|
|
26
|
+
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly",
|
|
27
|
+
"preview": "vite preview",
|
|
28
|
+
"test:unit": "vitest",
|
|
29
|
+
"test:unit:ui": "vitest --ui",
|
|
30
|
+
"test:unit:ci": "vitest run",
|
|
31
|
+
"--PROJECT--": "",
|
|
32
|
+
"tscheck": "vue-tsc --build --force",
|
|
33
|
+
"prettier": "prettier --check .",
|
|
34
|
+
"prettier-fix": "prettier . --write",
|
|
35
|
+
"lint": "eslint . --quiet",
|
|
36
|
+
"lint-fix": "npm run lint --fix --quiet",
|
|
37
|
+
"stylelint": "stylelint \"src/**/*.css\" \"**/*.scss\" \"**/*.vue\"",
|
|
38
|
+
"stylelint-fix": "stylelint \"src/**/*.css\" \"**/*.scss\" \"**/*.vue\" --fix",
|
|
39
|
+
"format": "npm run tscheck && npm run prettier && npm run lint && npm run stylelint",
|
|
40
|
+
"format-fix": "npm run tscheck && npm run prettier-fix && npm run lint-fix && npm run stylelint-fix"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@headlessui/vue": "^1.7.23",
|
|
44
|
+
"@heroicons/vue": "^2.2.0",
|
|
45
|
+
"@vueuse/core": "^14.0.0",
|
|
46
|
+
"sortablejs": "^1.15.6",
|
|
47
|
+
"vue": "^3.4.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/jsdom": "^21.1.7",
|
|
51
|
+
"@types/node": "^24.0.8",
|
|
52
|
+
"@types/sortablejs": "^1.15.9",
|
|
53
|
+
"@vitejs/plugin-vue": "^5.2.4",
|
|
54
|
+
"@vitest/eslint-plugin": "^1.3.5",
|
|
55
|
+
"@vitest/ui": "^2.1.9",
|
|
56
|
+
"@vue/eslint-config-prettier": "^10.2.0",
|
|
57
|
+
"@vue/eslint-config-typescript": "^14.5.1",
|
|
58
|
+
"@vue/test-utils": "^2.4.6",
|
|
59
|
+
"@vue/tsconfig": "^0.7.0",
|
|
60
|
+
"autoprefixer": "^10.4.21",
|
|
61
|
+
"eslint": "^9.30.0",
|
|
62
|
+
"eslint-plugin-tailwindcss": "^3.18.2",
|
|
63
|
+
"eslint-plugin-vue": "^9.33.0",
|
|
64
|
+
"jsdom": "^26.1.0",
|
|
65
|
+
"postcss": "^8.5.6",
|
|
66
|
+
"postcss-import": "^16.1.0",
|
|
67
|
+
"postcss-scss": "^4.0.9",
|
|
68
|
+
"prettier": "^3.6.2",
|
|
69
|
+
"sass": "^1.89.2",
|
|
70
|
+
"stylelint": "^16.21.0",
|
|
71
|
+
"stylelint-config-recess-order": "^7.1.0",
|
|
72
|
+
"stylelint-config-standard": "^38.0.0",
|
|
73
|
+
"stylelint-config-standard-scss": "^15.0.1",
|
|
74
|
+
"stylelint-config-standard-vue": "^1.0.0",
|
|
75
|
+
"tailwindcss": "^3.4.10",
|
|
76
|
+
"typescript": "~5.8.3",
|
|
77
|
+
"vite": "^5.4.19",
|
|
78
|
+
"vitest": "^2.1.9",
|
|
79
|
+
"vue-tsc": "^2.2.12",
|
|
80
|
+
"yup": "^1.7.1"
|
|
81
|
+
},
|
|
82
|
+
"peerDependencies": {
|
|
83
|
+
"vue": "^3.4.0"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Block Margin Utility Mixin
|
|
2
|
+
// Generates margin classes for top and bottom margins
|
|
3
|
+
// Usage: @include block-margin-classes;
|
|
4
|
+
|
|
5
|
+
@mixin block-margin-classes {
|
|
6
|
+
// Margin size variables
|
|
7
|
+
$sm-size: 2rem;
|
|
8
|
+
$md-size: 4rem;
|
|
9
|
+
$lg-size: 6rem;
|
|
10
|
+
|
|
11
|
+
// Top margin classes
|
|
12
|
+
&.margin-top-sm {
|
|
13
|
+
padding-top: $sm-size;
|
|
14
|
+
|
|
15
|
+
&::before {
|
|
16
|
+
top: 0;
|
|
17
|
+
height: $sm-size;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&.margin-top-md {
|
|
22
|
+
padding-top: $md-size;
|
|
23
|
+
|
|
24
|
+
&::before {
|
|
25
|
+
top: 0;
|
|
26
|
+
height: $md-size;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&.margin-top-lg {
|
|
31
|
+
padding-top: $lg-size;
|
|
32
|
+
|
|
33
|
+
&::before {
|
|
34
|
+
top: 0;
|
|
35
|
+
height: $lg-size;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Bottom margin classes
|
|
40
|
+
&.margin-bottom-sm {
|
|
41
|
+
padding-bottom: $sm-size;
|
|
42
|
+
|
|
43
|
+
&::after {
|
|
44
|
+
bottom: 0;
|
|
45
|
+
height: $sm-size;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&.margin-bottom-md {
|
|
50
|
+
padding-bottom: $md-size;
|
|
51
|
+
|
|
52
|
+
&::after {
|
|
53
|
+
bottom: 0;
|
|
54
|
+
height: $md-size;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&.margin-bottom-lg {
|
|
59
|
+
padding-bottom: $lg-size;
|
|
60
|
+
|
|
61
|
+
&::after {
|
|
62
|
+
bottom: 0;
|
|
63
|
+
height: $lg-size;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// No margin classes
|
|
68
|
+
&.margin-top-none::before,
|
|
69
|
+
&.margin-bottom-none::after {
|
|
70
|
+
display: none;
|
|
71
|
+
content: none;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:data-block-type="block.type"
|
|
4
|
+
draggable="true"
|
|
5
|
+
class="cursor-pointer rounded-md border bg-zinc-50 p-3 text-sm text-zinc-900 hover:border-zinc-400 hover:text-zinc-900"
|
|
6
|
+
@dragstart="(event) => handleDragStart(event, block)"
|
|
7
|
+
>
|
|
8
|
+
<!-- thumbnail image -->
|
|
9
|
+
<div v-if="thumbnailUrl" class="w-full overflow-hidden rounded-md bg-neutral-100">
|
|
10
|
+
<img
|
|
11
|
+
:src="thumbnailUrl"
|
|
12
|
+
:alt="block.label || block.type"
|
|
13
|
+
class="mx-auto mb-2 h-28 w-auto object-contain"
|
|
14
|
+
@error="thumbnailError = true"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
<!-- icon -->
|
|
18
|
+
<div v-else-if="block.icon" class="mb-2 flex h-28 w-full items-center justify-center rounded-md bg-zinc-200">
|
|
19
|
+
<span>Icon: {{ block.icon }}</span>
|
|
20
|
+
</div>
|
|
21
|
+
<!-- placeholder -->
|
|
22
|
+
<div v-else class="mb-2 flex h-28 w-full items-center justify-center rounded-md bg-zinc-200">
|
|
23
|
+
<CubeTransparentIcon class="size-6 text-zinc-400" />
|
|
24
|
+
</div>
|
|
25
|
+
<p class="font-bold">{{ block.label }}</p>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import { computed, defineProps, ref } from "vue";
|
|
31
|
+
import type { Block } from "../../types/Block";
|
|
32
|
+
import { getBlockThumbnailUrl } from "../../util/registry";
|
|
33
|
+
import { CubeTransparentIcon } from "@heroicons/vue/24/outline";
|
|
34
|
+
|
|
35
|
+
const props = defineProps<{
|
|
36
|
+
block: Block;
|
|
37
|
+
}>();
|
|
38
|
+
|
|
39
|
+
const thumbnailError = ref<boolean>(false);
|
|
40
|
+
const thumbnailUrl = computed(() => {
|
|
41
|
+
return getBlockThumbnailUrl(props.block.directory);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function handleDragStart(event: DragEvent, block: Block) {
|
|
45
|
+
if (!event.dataTransfer) return;
|
|
46
|
+
// Store the block type in dataTransfer
|
|
47
|
+
event.dataTransfer.setData("block-type", block.__name);
|
|
48
|
+
event.dataTransfer.effectAllowed = "move";
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="block-browser">
|
|
3
|
+
<div class="block-browser-header border-b bg-white px-5 py-3">
|
|
4
|
+
<input v-model="blockSearch" type="text" placeholder="Search blocks" class="form-control" />
|
|
5
|
+
</div>
|
|
6
|
+
<div v-if="!blockCount" class="p-5 text-center text-sm text-zinc-500">
|
|
7
|
+
<p>Create your first block to get started.</p>
|
|
8
|
+
<p class="mt-3">
|
|
9
|
+
<a
|
|
10
|
+
href="https://github.com/sano-io/page-builder/tree/main/blocks"
|
|
11
|
+
target="_blank"
|
|
12
|
+
class="text-blue-600 underline underline-offset-2 hover:text-blue-800"
|
|
13
|
+
>How to create a block</a
|
|
14
|
+
>
|
|
15
|
+
</p>
|
|
16
|
+
</div>
|
|
17
|
+
<div v-else-if="!filteredBlocks.length" class="p-5 text-center text-sm text-zinc-500">No blocks found</div>
|
|
18
|
+
<div v-else id="available-blocks-list" class="grid grid-cols-1 gap-3 p-5">
|
|
19
|
+
<AddBlockItem v-for="block in filteredBlocks" :key="block.type" :block="block" />
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script setup lang="ts">
|
|
25
|
+
import { computed, onMounted, ref } from "vue";
|
|
26
|
+
import { pageBuilderBlocks } from "../../util/registry";
|
|
27
|
+
import AddBlockItem from "../AddBlockItem/AddBlockItem.vue";
|
|
28
|
+
import type { Block } from "../../types/Block";
|
|
29
|
+
import Sortable from "sortablejs";
|
|
30
|
+
|
|
31
|
+
const blockSearch = ref("");
|
|
32
|
+
const filteredBlocks = computed(() => {
|
|
33
|
+
if (!pageBuilderBlocks.value) return [];
|
|
34
|
+
return Object.values(pageBuilderBlocks.value).filter((block: Block) => {
|
|
35
|
+
// against block name and label
|
|
36
|
+
return (
|
|
37
|
+
block.type?.toLowerCase().includes(blockSearch.value.toLowerCase()) ||
|
|
38
|
+
block.label?.toLowerCase().includes(blockSearch.value.toLowerCase())
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const blockCount = computed(() => {
|
|
44
|
+
if (!pageBuilderBlocks.value) return 0;
|
|
45
|
+
return Object.values(pageBuilderBlocks.value).length;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function initSortable() {
|
|
49
|
+
const sortableBlocksWrapper = document.getElementById("available-blocks-list");
|
|
50
|
+
if (!sortableBlocksWrapper) return;
|
|
51
|
+
if (!blockCount.value) return;
|
|
52
|
+
new Sortable(sortableBlocksWrapper, {
|
|
53
|
+
animation: 150,
|
|
54
|
+
ghostClass: "sortable-ghost",
|
|
55
|
+
chosenClass: "sortable-chosen",
|
|
56
|
+
dragClass: "sortable-drag",
|
|
57
|
+
group: {
|
|
58
|
+
name: "page-blocks",
|
|
59
|
+
pull: "clone",
|
|
60
|
+
put: false,
|
|
61
|
+
},
|
|
62
|
+
sort: false,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onMounted(() => {
|
|
67
|
+
initSortable();
|
|
68
|
+
});
|
|
69
|
+
</script>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="block-wrapper"
|
|
4
|
+
:class="{
|
|
5
|
+
[getMarginClass(block)]: true,
|
|
6
|
+
'active-block': activeBlock?.id === block.id,
|
|
7
|
+
'hovered-block': hoveredBlockId === block.id,
|
|
8
|
+
}"
|
|
9
|
+
:data-block-index="blockIndex"
|
|
10
|
+
:data-block-id="block.id"
|
|
11
|
+
>
|
|
12
|
+
<div
|
|
13
|
+
v-if="activeBlock?.id === block.id"
|
|
14
|
+
class="absolute -top-3 right-4 z-10 rounded-full bg-blue-500 px-2 py-1 text-xs text-white"
|
|
15
|
+
>
|
|
16
|
+
<p>Editing</p>
|
|
17
|
+
</div>
|
|
18
|
+
<div
|
|
19
|
+
v-if="pageBuilderBlocks[toCamelCase(block.type)]"
|
|
20
|
+
class="block-component"
|
|
21
|
+
@mouseenter="emit('hoverBlock', block.id)"
|
|
22
|
+
@mouseleave="emit('hoverBlock', null)"
|
|
23
|
+
@click="emit('clickBlock', block)"
|
|
24
|
+
>
|
|
25
|
+
<component :is="pageBuilderBlocks[toCamelCase(block.type)]" v-bind="block" ref="blockComponentRef" />
|
|
26
|
+
</div>
|
|
27
|
+
<div
|
|
28
|
+
v-else
|
|
29
|
+
class="block-not-found px-3 py-2"
|
|
30
|
+
@mouseenter="emit('hoverBlock', block.id)"
|
|
31
|
+
@mouseleave="emit('hoverBlock', null)"
|
|
32
|
+
>
|
|
33
|
+
<div class="rounded-lg bg-zinc-200 p-5 px-3 text-center text-sm text-zinc-600">
|
|
34
|
+
<p class="mb-2">Block not registered</p>
|
|
35
|
+
<span class="rounded-full bg-zinc-300 px-2 py-1 text-zinc-600">
|
|
36
|
+
{{ toCamelCase(block.type) }}
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script setup lang="ts">
|
|
44
|
+
import { useTemplateRef } from "vue";
|
|
45
|
+
import { onClickOutside } from "@vueuse/core";
|
|
46
|
+
import { pageBuilderBlocks } from "../../util/registry";
|
|
47
|
+
import type { Block } from "../../types/Block";
|
|
48
|
+
import { toCamelCase } from "../../util/helpers";
|
|
49
|
+
import { onKeyStroke } from "@vueuse/core";
|
|
50
|
+
|
|
51
|
+
const emit = defineEmits<{
|
|
52
|
+
(e: "hoverBlock", id: string | null): void;
|
|
53
|
+
(e: "clickBlock", block: Block | null): void;
|
|
54
|
+
}>();
|
|
55
|
+
|
|
56
|
+
const props = defineProps<{
|
|
57
|
+
block: Block;
|
|
58
|
+
blockIndex: number;
|
|
59
|
+
activeBlock: Block | null;
|
|
60
|
+
hoveredBlockId: string | null;
|
|
61
|
+
}>();
|
|
62
|
+
|
|
63
|
+
const blockComponentRef = useTemplateRef<HTMLElement | null>("blockComponentRef");
|
|
64
|
+
|
|
65
|
+
// Get the margin class for the block
|
|
66
|
+
// Margin is an object with top and bottom properties
|
|
67
|
+
// margin classses are formatted as `margin-<direction>-<size>`
|
|
68
|
+
function getMarginClass(block: Block): string {
|
|
69
|
+
const top = block.margin?.top || "none";
|
|
70
|
+
const bottom = block.margin?.bottom || "none";
|
|
71
|
+
|
|
72
|
+
// Map margin sizes to custom class names: none, sm, md, lg, xl
|
|
73
|
+
const getClass = (size: string, direction: "top" | "bottom"): string => {
|
|
74
|
+
const normalizedSize = size === "small" ? "sm" : size === "medium" ? "md" : size === "large" ? "lg" : size;
|
|
75
|
+
return `margin-${direction}-${normalizedSize}`;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return [getClass(top, "top"), getClass(bottom, "bottom")].join(" ");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Click outside detection
|
|
82
|
+
onClickOutside(
|
|
83
|
+
blockComponentRef,
|
|
84
|
+
() => {
|
|
85
|
+
// Unset active block
|
|
86
|
+
emit("clickBlock", null);
|
|
87
|
+
},
|
|
88
|
+
{ ignore: ["#page-builder-sidebar", "#page-builder-resize-handle", "button"] }
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// On escape key press
|
|
92
|
+
onKeyStroke("Escape", () => {
|
|
93
|
+
if (!props.activeBlock) return;
|
|
94
|
+
// Unset active block
|
|
95
|
+
emit("clickBlock", null);
|
|
96
|
+
});
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<style scoped lang="scss">
|
|
100
|
+
@use "../../assets/styles/mixins" as *;
|
|
101
|
+
|
|
102
|
+
.block-wrapper {
|
|
103
|
+
position: relative;
|
|
104
|
+
transition: all 0.3s ease;
|
|
105
|
+
|
|
106
|
+
// Margin spacing overlay
|
|
107
|
+
&::before,
|
|
108
|
+
&::after {
|
|
109
|
+
position: absolute;
|
|
110
|
+
left: 0;
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
width: 100%;
|
|
115
|
+
height: 0;
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
font-weight: 500;
|
|
118
|
+
color: #888017;
|
|
119
|
+
content: "Spacing";
|
|
120
|
+
background-color: #f7efac;
|
|
121
|
+
border: 2px dashed #d2c564;
|
|
122
|
+
border-radius: 7px;
|
|
123
|
+
opacity: 0;
|
|
124
|
+
transform: scaleY(0.9) scaleX(0.98);
|
|
125
|
+
transition: all 0.3s ease;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.block-component {
|
|
129
|
+
position: relative;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
|
|
132
|
+
// Highlight block overlay
|
|
133
|
+
&::before {
|
|
134
|
+
position: absolute;
|
|
135
|
+
top: 0;
|
|
136
|
+
left: 0;
|
|
137
|
+
z-index: 2;
|
|
138
|
+
width: 100%;
|
|
139
|
+
height: 100%;
|
|
140
|
+
pointer-events: none;
|
|
141
|
+
outline: 2px dashed #638ef1;
|
|
142
|
+
outline-offset: -2px;
|
|
143
|
+
content: "";
|
|
144
|
+
background-color: #9fd0f643;
|
|
145
|
+
opacity: 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Active state
|
|
150
|
+
&.active-block {
|
|
151
|
+
.block-component {
|
|
152
|
+
&::before {
|
|
153
|
+
background-color: #9fd0f643;
|
|
154
|
+
border-color: #638ef1;
|
|
155
|
+
opacity: 1;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Show the margin spacing overlay
|
|
160
|
+
&::after,
|
|
161
|
+
&::before {
|
|
162
|
+
opacity: 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Hovered state
|
|
167
|
+
&.hovered-block {
|
|
168
|
+
.block-component {
|
|
169
|
+
&::before {
|
|
170
|
+
background-color: #9fd0f643;
|
|
171
|
+
border-color: #638ef1;
|
|
172
|
+
opacity: 1;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Show the margin spacing overlay
|
|
177
|
+
&::after,
|
|
178
|
+
&::before {
|
|
179
|
+
opacity: 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Block margin classes - generated using mixin
|
|
184
|
+
@include block-margin-classes;
|
|
185
|
+
}
|
|
186
|
+
</style>
|