Allow in-preview editing of paragraphs and headings
This commit is contained in:
parent
c6304ca321
commit
b522726afc
|
@ -589,6 +589,7 @@
|
|||
"icon": "TextParagraph",
|
||||
"illegalChildren": ["section"],
|
||||
"showSettingsBar": true,
|
||||
"editable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
|
@ -695,6 +696,7 @@
|
|||
"description": "A component for displaying heading text",
|
||||
"illegalChildren": ["section"],
|
||||
"showSettingsBar": true,
|
||||
"editable": true,
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
|
|
|
@ -60,21 +60,32 @@
|
|||
$: instanceKey = JSON.stringify(rawProps)
|
||||
$: updateComponentProps(rawProps, instanceKey, $context)
|
||||
$: selected =
|
||||
$builderStore.inBuilder &&
|
||||
$builderStore.selectedComponentId === instance._id
|
||||
$builderStore.inBuilder && $builderStore.selectedComponentId === id
|
||||
$: inSelectedPath = $builderStore.selectedComponentPath?.includes(id)
|
||||
$: evaluateConditions(enrichedSettings?._conditions)
|
||||
$: componentSettings = { ...enrichedSettings, ...conditionalSettings }
|
||||
$: renderKey = `${propsHash}-${emptyState}`
|
||||
$: editable = definition.editable
|
||||
$: editing = editable && selected && $builderStore.editMode
|
||||
$: draggable = interactive && !isLayout && !isScreen && !editing
|
||||
$: droppable = interactive && !isLayout && !isScreen
|
||||
|
||||
// Update component context
|
||||
$: componentStore.set({
|
||||
id,
|
||||
children: children.length,
|
||||
styles: { ...instance._styles, id, empty: emptyState, interactive },
|
||||
styles: {
|
||||
...instance._styles,
|
||||
id,
|
||||
empty: emptyState,
|
||||
interactive,
|
||||
draggable,
|
||||
editable,
|
||||
},
|
||||
empty: emptyState,
|
||||
selected,
|
||||
name,
|
||||
editing,
|
||||
})
|
||||
|
||||
const getRawProps = instance => {
|
||||
|
@ -171,10 +182,6 @@
|
|||
conditionalSettings = result.settingUpdates
|
||||
visible = nextVisible
|
||||
}
|
||||
|
||||
// Drag and drop helper tags
|
||||
$: draggable = interactive && !isLayout && !isScreen
|
||||
$: droppable = interactive && !isLayout && !isScreen
|
||||
</script>
|
||||
|
||||
{#key renderKey}
|
||||
|
@ -187,6 +194,7 @@
|
|||
class:droppable
|
||||
class:empty
|
||||
class:interactive
|
||||
class:editing
|
||||
data-id={id}
|
||||
data-name={name}
|
||||
>
|
||||
|
@ -213,4 +221,7 @@
|
|||
.draggable :global(*:hover) {
|
||||
cursor: grab;
|
||||
}
|
||||
.editing :global(*:hover) {
|
||||
cursor: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -36,21 +36,34 @@
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert contenteditable HTML to text and save
|
||||
const updateText = e => {
|
||||
const html = e.target.innerHTML
|
||||
const sanitized = html
|
||||
.replace(/<\/div><div>/gi, "\n")
|
||||
.replace(/<div>/gi, "")
|
||||
.replace(/<\/div>/gi, "")
|
||||
.replace(/<br>/gi, "")
|
||||
builderStore.actions.updateProp("text", sanitized)
|
||||
}
|
||||
</script>
|
||||
|
||||
<h1
|
||||
<div
|
||||
contenteditable={$component.editing}
|
||||
use:styleable={styles}
|
||||
class:placeholder
|
||||
class:bold
|
||||
class:italic
|
||||
class:underline
|
||||
class="spectrum-Heading {sizeClass} {alignClass}"
|
||||
on:blur={$component.editing ? updateText : null}
|
||||
>
|
||||
{componentText}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
div {
|
||||
white-space: pre-wrap;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
|
@ -35,21 +35,34 @@
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert contenteditable HTML to text and save
|
||||
const updateText = e => {
|
||||
const html = e.target.innerHTML
|
||||
const sanitized = html
|
||||
.replace(/<\/div><div>/gi, "\n")
|
||||
.replace(/<div>/gi, "")
|
||||
.replace(/<\/div>/gi, "")
|
||||
.replace(/<br>/gi, "")
|
||||
builderStore.actions.updateProp("text", sanitized)
|
||||
}
|
||||
</script>
|
||||
|
||||
<p
|
||||
<div
|
||||
contenteditable={$component.editing}
|
||||
use:styleable={styles}
|
||||
class:placeholder
|
||||
class:bold
|
||||
class:italic
|
||||
class:underline
|
||||
class="spectrum-Body {sizeClass} {alignClass}"
|
||||
on:blur={$component.editing ? updateText : null}
|
||||
>
|
||||
{componentText}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
p {
|
||||
div {
|
||||
display: inline-block;
|
||||
white-space: pre-wrap;
|
||||
margin: 0;
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
|
||||
<div
|
||||
in:fade={{
|
||||
delay: transition ? 50 : 0,
|
||||
delay: transition ? 130 : 0,
|
||||
duration: transition ? 130 : 0,
|
||||
}}
|
||||
out:fade={{ duration: transition ? 130 : 0 }}
|
||||
class="indicator"
|
||||
class:flipped
|
||||
class:line
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<script>
|
||||
import { builderStore } from "stores"
|
||||
import IndicatorSet from "./IndicatorSet.svelte"
|
||||
|
||||
$: color = $builderStore.editMode
|
||||
? "var(--spectrum-global-color-static-green-500)"
|
||||
: "var(--spectrum-global-color-static-blue-600)"
|
||||
</script>
|
||||
|
||||
<IndicatorSet
|
||||
componentId={$builderStore.selectedComponentId}
|
||||
color="var(--spectrum-global-color-static-blue-600)"
|
||||
{color}
|
||||
zIndex="910"
|
||||
transition
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { writable, derived } from "svelte/store"
|
||||
import { writable, derived, get } from "svelte/store"
|
||||
import Manifest from "manifest.json"
|
||||
import { findComponentById, findComponentPathById } from "../utils/components"
|
||||
import { pingEndUser } from "../api"
|
||||
|
@ -18,6 +18,7 @@ const createBuilderStore = () => {
|
|||
layout: null,
|
||||
screen: null,
|
||||
selectedComponentId: null,
|
||||
editMode: false,
|
||||
previewId: null,
|
||||
previewType: null,
|
||||
selectedPath: [],
|
||||
|
@ -54,6 +55,10 @@ const createBuilderStore = () => {
|
|||
|
||||
const actions = {
|
||||
selectComponent: id => {
|
||||
if (id === get(writableStore).selectedComponentId) {
|
||||
return
|
||||
}
|
||||
writableStore.update(state => ({ ...state, editMode: false }))
|
||||
dispatchEvent("select-component", { id })
|
||||
},
|
||||
updateProp: (prop, value) => {
|
||||
|
@ -69,10 +74,7 @@ const createBuilderStore = () => {
|
|||
pingEndUser()
|
||||
},
|
||||
setSelectedPath: path => {
|
||||
writableStore.update(state => {
|
||||
state.selectedPath = path
|
||||
return state
|
||||
})
|
||||
writableStore.update(state => ({ ...state, selectedPath: path }))
|
||||
},
|
||||
moveComponent: (componentId, destinationComponentId, mode) => {
|
||||
dispatchEvent("move-component", {
|
||||
|
@ -82,10 +84,10 @@ const createBuilderStore = () => {
|
|||
})
|
||||
},
|
||||
setDragging: dragging => {
|
||||
writableStore.update(state => {
|
||||
state.isDragging = dragging
|
||||
return state
|
||||
})
|
||||
writableStore.update(state => ({ ...state, isDragging: dragging }))
|
||||
},
|
||||
setEditMode: enabled => {
|
||||
writableStore.update(state => ({ ...state, editMode: enabled }))
|
||||
},
|
||||
}
|
||||
return {
|
||||
|
|
|
@ -22,12 +22,7 @@ export const styleable = (node, styles = {}) => {
|
|||
let applyNormalStyles
|
||||
let applyHoverStyles
|
||||
let selectComponent
|
||||
|
||||
// Allow dragging if required
|
||||
const parent = node.closest(".component")
|
||||
if (parent && parent.classList.contains("draggable")) {
|
||||
node.setAttribute("draggable", true)
|
||||
}
|
||||
let editComponent
|
||||
|
||||
// Creates event listeners and applies initial styles
|
||||
const setupStyles = (newStyles = {}) => {
|
||||
|
@ -46,6 +41,9 @@ export const styleable = (node, styles = {}) => {
|
|||
...(newStyles.hover || {}),
|
||||
}
|
||||
|
||||
// Allow dragging if required
|
||||
node.setAttribute("draggable", !!styles.draggable)
|
||||
|
||||
// Applies a style string to a DOM node
|
||||
const applyStyles = styleString => {
|
||||
node.style = styleString
|
||||
|
@ -72,13 +70,25 @@ export const styleable = (node, styles = {}) => {
|
|||
return false
|
||||
}
|
||||
|
||||
// Handler to start editing a component (if applicable) when double
|
||||
// clicking in the builder preview
|
||||
editComponent = event => {
|
||||
if (newStyles.interactive && newStyles.editable) {
|
||||
builderStore.actions.setEditMode(true)
|
||||
}
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return false
|
||||
}
|
||||
|
||||
// Add listeners to toggle hover styles
|
||||
node.addEventListener("mouseover", applyHoverStyles)
|
||||
node.addEventListener("mouseout", applyNormalStyles)
|
||||
|
||||
// Add builder preview click listener
|
||||
// Add builder preview listeners
|
||||
if (get(builderStore).inBuilder) {
|
||||
node.addEventListener("click", selectComponent, false)
|
||||
node.addEventListener("dblclick", editComponent, false)
|
||||
}
|
||||
|
||||
// Apply initial normal styles
|
||||
|
@ -90,9 +100,10 @@ export const styleable = (node, styles = {}) => {
|
|||
node.removeEventListener("mouseover", applyHoverStyles)
|
||||
node.removeEventListener("mouseout", applyNormalStyles)
|
||||
|
||||
// Remove builder preview click listener
|
||||
// Remove builder preview listeners
|
||||
if (get(builderStore).inBuilder) {
|
||||
node.removeEventListener("click", selectComponent)
|
||||
node.removeEventListener("dblclick", editComponent)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue