More adaptable button interface
This commit is contained in:
parent
4948cf9c3b
commit
191778b440
26
package-lock.json
generated
26
package-lock.json
generated
|
@ -1461,6 +1461,21 @@
|
|||
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.153",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.153.tgz",
|
||||
"integrity": "sha512-lYniGRiRfZf2gGAR9cfRC3Pi5+Q1ziJCKqPmjZocigrSJUVPWf7st1BtSJ8JOeK0FLXVndQ1IjUjTco9CXGo/Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash-es": {
|
||||
"version": "4.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.3.tgz",
|
||||
"integrity": "sha512-iHI0i7ZAL1qepz1Y7f3EKg/zUMDwDfTzitx+AlHhJJvXwenP682ZyGbgPSc5Ej3eEAKVbNWKFuwOadCj5vBbYQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||
|
@ -9944,6 +9959,11 @@
|
|||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
|
||||
"integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ=="
|
||||
},
|
||||
"lodash._arraycopy": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz",
|
||||
|
@ -14887,9 +14907,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
|
||||
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
|
||||
"version": "3.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
|
||||
"integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
"dependencies": {
|
||||
"@vue/composition-api": "^0.5.0",
|
||||
"core-js": "^3.6.4",
|
||||
"lodash-es": "^4.17.15",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.19",
|
||||
"@types/lodash-es": "^4.17.3",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"@vue/cli-plugin-babel": "~4.3.0",
|
||||
|
@ -35,7 +37,7 @@
|
|||
"geckodriver": "^1.19.1",
|
||||
"sass": "^1.26.3",
|
||||
"sass-loader": "^8.0.2",
|
||||
"typescript": "~3.8.3",
|
||||
"typescript": "~3.9.3",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
|
|
242
src/ResizeObserver.d.ts
vendored
Normal file
242
src/ResizeObserver.d.ts
vendored
Normal file
|
@ -0,0 +1,242 @@
|
|||
/**
|
||||
* The **ResizeObserver** interface reports changes to the dimensions of an
|
||||
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element)'s content
|
||||
* or border box, or the bounding box of an
|
||||
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
|
||||
*
|
||||
* > **Note**: The content box is the box in which content can be placed,
|
||||
* > meaning the border box minus the padding and border width. The border box
|
||||
* > encompasses the content, padding, and border. See
|
||||
* > [The box model](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/The_box_model)
|
||||
* > for further explanation.
|
||||
*
|
||||
* `ResizeObserver` avoids infinite callback loops and cyclic dependencies that
|
||||
* are often created when resizing via a callback function. It does this by only
|
||||
* processing elements deeper in the DOM in subsequent frames. Implementations
|
||||
* should, if they follow the specification, invoke resize events before paint
|
||||
* and after layout.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
|
||||
*/
|
||||
declare class ResizeObserver {
|
||||
/**
|
||||
* The **ResizeObserver** constructor creates a new `ResizeObserver` object,
|
||||
* which can be used to report changes to the content or border box of an
|
||||
* `Element` or the bounding box of an `SVGElement`.
|
||||
*
|
||||
* @example
|
||||
* var ResizeObserver = new ResizeObserver(callback)
|
||||
*
|
||||
* @param callback
|
||||
* The function called whenever an observed resize occurs. The function is
|
||||
* called with two parameters:
|
||||
* * **entries**
|
||||
* An array of
|
||||
* [ResizeObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
|
||||
* objects that can be used to access the new dimensions of the element
|
||||
* after each change.
|
||||
* * **observer**
|
||||
* A reference to the `ResizeObserver` itself, so it will definitely be
|
||||
* accessible from inside the callback, should you need it. This could be
|
||||
* used for example to automatically unobserve the observer when a certain
|
||||
* condition is reached, but you can omit it if you don't need it.
|
||||
*
|
||||
* The callback will generally follow a pattern along the lines of:
|
||||
* ```js
|
||||
* function(entries, observer) {
|
||||
* for (let entry of entries) {
|
||||
* // Do something to each entry
|
||||
* // and possibly something to the observer itself
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The following snippet is taken from the
|
||||
* [resize-observer-text.html](https://mdn.github.io/dom-examples/resize-observer/resize-observer-text.html)
|
||||
* ([see source](https://github.com/mdn/dom-examples/blob/master/resize-observer/resize-observer-text.html))
|
||||
* example:
|
||||
* @example
|
||||
* const resizeObserver = new ResizeObserver(entries => {
|
||||
* for (let entry of entries) {
|
||||
* if(entry.contentBoxSize) {
|
||||
* h1Elem.style.fontSize = Math.max(1.5, entry.contentBoxSize.inlineSize/200) + 'rem';
|
||||
* pElem.style.fontSize = Math.max(1, entry.contentBoxSize.inlineSize/600) + 'rem';
|
||||
* } else {
|
||||
* h1Elem.style.fontSize = Math.max(1.5, entry.contentRect.width/200) + 'rem';
|
||||
* pElem.style.fontSize = Math.max(1, entry.contentRect.width/600) + 'rem';
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* resizeObserver.observe(divElem);
|
||||
*/
|
||||
constructor(callback: ResizeObserverCallback);
|
||||
|
||||
/**
|
||||
* The **disconnect()** method of the
|
||||
* [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
|
||||
* interface unobserves all observed
|
||||
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
|
||||
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
|
||||
* targets.
|
||||
*/
|
||||
disconnect: () => void;
|
||||
|
||||
/**
|
||||
* The `observe()` method of the
|
||||
* [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
|
||||
* interface starts observing the specified
|
||||
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
|
||||
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
|
||||
*
|
||||
* @example
|
||||
* resizeObserver.observe(target, options);
|
||||
*
|
||||
* @param target
|
||||
* A reference to an
|
||||
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
|
||||
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
|
||||
* to be observed.
|
||||
*
|
||||
* @param options
|
||||
* An options object allowing you to set options for the observation.
|
||||
* Currently this only has one possible option that can be set.
|
||||
*/
|
||||
observe: (target: Element, options?: ResizeObserverObserveOptions) => void;
|
||||
|
||||
/**
|
||||
* The **unobserve()** method of the
|
||||
* [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
|
||||
* interface ends the observing of a specified
|
||||
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
|
||||
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
|
||||
*/
|
||||
unobserve: (target: Element) => void;
|
||||
}
|
||||
|
||||
interface ResizeObserverObserveOptions {
|
||||
/**
|
||||
* Sets which box model the observer will observe changes to. Possible values
|
||||
* are `content-box` (the default), and `border-box`.
|
||||
*
|
||||
* @default "content-box"
|
||||
*/
|
||||
box?: "content-box" | "border-box";
|
||||
}
|
||||
|
||||
/**
|
||||
* The function called whenever an observed resize occurs. The function is
|
||||
* called with two parameters:
|
||||
*
|
||||
* @param entries
|
||||
* An array of
|
||||
* [ResizeObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
|
||||
* objects that can be used to access the new dimensions of the element after
|
||||
* each change.
|
||||
*
|
||||
* @param observer
|
||||
* A reference to the `ResizeObserver` itself, so it will definitely be
|
||||
* accessible from inside the callback, should you need it. This could be used
|
||||
* for example to automatically unobserve the observer when a certain condition
|
||||
* is reached, but you can omit it if you don't need it.
|
||||
*
|
||||
* The callback will generally follow a pattern along the lines of:
|
||||
* @example
|
||||
* function(entries, observer) {
|
||||
* for (let entry of entries) {
|
||||
* // Do something to each entry
|
||||
* // and possibly something to the observer itself
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @example
|
||||
* const resizeObserver = new ResizeObserver(entries => {
|
||||
* for (let entry of entries) {
|
||||
* if(entry.contentBoxSize) {
|
||||
* h1Elem.style.fontSize = Math.max(1.5, entry.contentBoxSize.inlineSize/200) + 'rem';
|
||||
* pElem.style.fontSize = Math.max(1, entry.contentBoxSize.inlineSize/600) + 'rem';
|
||||
* } else {
|
||||
* h1Elem.style.fontSize = Math.max(1.5, entry.contentRect.width/200) + 'rem';
|
||||
* pElem.style.fontSize = Math.max(1, entry.contentRect.width/600) + 'rem';
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* resizeObserver.observe(divElem);
|
||||
*/
|
||||
type ResizeObserverCallback = (
|
||||
entries: ResizeObserverEntry[],
|
||||
observer: ResizeObserver,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* The **ResizeObserverEntry** interface represents the object passed to the
|
||||
* [ResizeObserver()](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver)
|
||||
* constructor's callback function, which allows you to access the new
|
||||
* dimensions of the
|
||||
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
|
||||
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
|
||||
* being observed.
|
||||
*/
|
||||
interface ResizeObserverEntry {
|
||||
/**
|
||||
* An object containing the new border box size of the observed element when
|
||||
* the callback is run.
|
||||
*/
|
||||
readonly borderBoxSize: ResizeObserverEntryBoxSize;
|
||||
|
||||
/**
|
||||
* An object containing the new content box size of the observed element when
|
||||
* the callback is run.
|
||||
*/
|
||||
readonly contentBoxSize: ResizeObserverEntryBoxSize;
|
||||
|
||||
/**
|
||||
* A [DOMRectReadOnly](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly)
|
||||
* object containing the new size of the observed element when the callback is
|
||||
* run. Note that this is better supported than the above two properties, but
|
||||
* it is left over from an earlier implementation of the Resize Observer API,
|
||||
* is still included in the spec for web compat reasons, and may be deprecated
|
||||
* in future versions.
|
||||
*/
|
||||
// node_modules/typescript/lib/lib.dom.d.ts
|
||||
readonly contentRect: DOMRectReadOnly;
|
||||
|
||||
/**
|
||||
* A reference to the
|
||||
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
|
||||
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
|
||||
* being observed.
|
||||
*/
|
||||
readonly target: Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* The **borderBoxSize** read-only property of the
|
||||
* [ResizeObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
|
||||
* interface returns an object containing the new border box size of the
|
||||
* observed element when the callback is run.
|
||||
*/
|
||||
interface ResizeObserverEntryBoxSize {
|
||||
/**
|
||||
* The length of the observed element's border box in the block dimension. For
|
||||
* boxes with a horizontal
|
||||
* [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode),
|
||||
* this is the vertical dimension, or height; if the writing-mode is vertical,
|
||||
* this is the horizontal dimension, or width.
|
||||
*/
|
||||
blockSize: number;
|
||||
|
||||
/**
|
||||
* The length of the observed element's border box in the inline dimension.
|
||||
* For boxes with a horizontal
|
||||
* [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode),
|
||||
* this is the horizontal dimension, or width; if the writing-mode is
|
||||
* vertical, this is the vertical dimension, or height.
|
||||
*/
|
||||
inlineSize: number;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
ResizeObserver: typeof ResizeObserver;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
.sb-main {
|
||||
padding: 50px 20px;
|
||||
|
||||
position: relative;
|
||||
background-color: var(--bg);
|
||||
padding: 50px 40px;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import {
|
|||
reactive,
|
||||
ref,
|
||||
PropType,
|
||||
Ref,
|
||||
watch,
|
||||
} from '@vue/composition-api';
|
||||
import {
|
||||
model,
|
||||
|
@ -11,9 +13,11 @@ import {
|
|||
Block,
|
||||
SbMode,
|
||||
Mode,
|
||||
EditorDimensions,
|
||||
BlockDefinition,
|
||||
BlockLibraryDefinition,
|
||||
BlockLibrary,
|
||||
useResizeObserver,
|
||||
} from '@components/TreeElement';
|
||||
|
||||
import SbBlock from '@internal/Block';
|
||||
|
@ -51,6 +55,9 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
setup(props: SchlechtenburgProps) {
|
||||
const el: Ref<null|HTMLElement> = ref(null);
|
||||
useResizeObserver(el, EditorDimensions);
|
||||
|
||||
const mode = ref(props.mode);
|
||||
provide(Mode, mode);
|
||||
|
||||
|
@ -73,7 +80,10 @@ export default defineComponent({
|
|||
provide(BlockLibrary, blockLibrary);
|
||||
|
||||
return () => (
|
||||
<div class="sb-main">
|
||||
<div
|
||||
class="sb-main"
|
||||
ref={el}
|
||||
>
|
||||
<SbBlock
|
||||
block={props.block}
|
||||
eventUpdate={props.eventUpdate}
|
||||
|
|
|
@ -4,6 +4,8 @@ import {
|
|||
inject,
|
||||
reactive,
|
||||
computed,
|
||||
watch,
|
||||
provide,
|
||||
} from '@vue/composition-api';
|
||||
|
||||
export interface BlockDefinition {
|
||||
|
@ -58,6 +60,53 @@ export function useDynamicBlocks() {
|
|||
};
|
||||
}
|
||||
|
||||
interface BlockRect {
|
||||
height: number;
|
||||
width: number;
|
||||
left: number;
|
||||
top: number;
|
||||
}
|
||||
|
||||
export const BlockDimensions = Symbol('Schlechtenburg block dimensions');
|
||||
export const EditorDimensions = Symbol('Schlechtenburg editor dimensions');
|
||||
export function useResizeObserver(el: Ref<null|HTMLElement>, symbol: symbol) {
|
||||
const dimensions: Ref<null|BlockRect> = ref(null);
|
||||
provide(symbol, dimensions);
|
||||
const triggerSizeCalculation = () => {
|
||||
if (!el.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clientRect = el.value.getBoundingClientRect();
|
||||
dimensions.value = {
|
||||
width: clientRect.width,
|
||||
height: clientRect.height,
|
||||
left: el.value.offsetLeft,
|
||||
top: el.value.offsetTop,
|
||||
};
|
||||
};
|
||||
|
||||
const resizeObserver = new ResizeObserver(triggerSizeCalculation);
|
||||
const mutationObserver = new MutationObserver(triggerSizeCalculation);
|
||||
|
||||
watch(el, () => {
|
||||
if (!el.value) {
|
||||
return;
|
||||
}
|
||||
resizeObserver.observe(el.value);
|
||||
mutationObserver.observe(el.value, { attributes: true, childList: false, subtree: false });
|
||||
});
|
||||
|
||||
return { triggerSizeCalculation, dimensions };
|
||||
}
|
||||
|
||||
export function useBlockSizing() {
|
||||
const editorDimensions: Ref<BlockRect|null> = inject(EditorDimensions, ref(null));
|
||||
const blockDimensions: Ref<BlockRect|null> = inject(BlockDimensions, ref(null));
|
||||
|
||||
return { editorDimensions, blockDimensions };
|
||||
}
|
||||
|
||||
export const ActiveBlock = Symbol('Schlechtenburg active block');
|
||||
export function useActivation(currentBlockId: string) {
|
||||
const activeBlockId: Ref<string|null> = inject(ActiveBlock, ref(null));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.sb-block {
|
||||
position: relative;
|
||||
$block: &;
|
||||
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-items: stretch;
|
||||
|
@ -10,6 +11,14 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
&__edit-cover {
|
||||
}
|
||||
|
||||
> .sb-block-ordering {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&_active {
|
||||
outline: 1px solid var(--grey-2);
|
||||
|
||||
|
@ -18,25 +27,10 @@
|
|||
pointer-events: all;
|
||||
outline: 1px solid var(--grey-2);
|
||||
}
|
||||
}
|
||||
|
||||
&__edit-cover {
|
||||
}
|
||||
|
||||
&__remove {
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 0;
|
||||
max-height: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&__sliders {
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
> .sb-block-ordering {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
watch,
|
||||
PropType,
|
||||
ref,
|
||||
Ref,
|
||||
} from '@vue/composition-api';
|
||||
import {
|
||||
Block,
|
||||
useDynamicBlocks,
|
||||
useActivation,
|
||||
SbMode,
|
||||
BlockDimensions,
|
||||
useResizeObserver,
|
||||
} from '@components/TreeElement';
|
||||
|
||||
import SbButton from './Button';
|
||||
import SbBlockOrdering from './BlockOrdering';
|
||||
|
||||
import './Block.scss';
|
||||
|
||||
|
@ -20,6 +25,9 @@ interface BlockProps {
|
|||
eventInsertBlock: (b?: Block) => void;
|
||||
eventAppendBlock: (b?: Block) => void;
|
||||
eventRemoveBlock: () => void;
|
||||
eventMoveUp: () => void;
|
||||
eventMoveDown: () => void;
|
||||
sortable: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -30,30 +38,27 @@ export default defineComponent({
|
|||
type: (null as unknown) as PropType<Block>,
|
||||
required: true,
|
||||
},
|
||||
sortable: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
eventUpdate: { type: Function, default: () => {} },
|
||||
eventInsertBlock: { type: Function, default: () => {} },
|
||||
eventAppendBlock: { type: Function, default: () => {} },
|
||||
eventRemoveBlock: { type: Function, default: () => {} },
|
||||
eventMoveUp: { type: Function, default: () => {} },
|
||||
eventMoveDown: { type: Function, default: () => {} },
|
||||
},
|
||||
|
||||
setup(props: BlockProps, context) {
|
||||
const { isActive, activate } = useActivation(props.block.blockId);
|
||||
const el: Ref<null|HTMLElement> = ref(null);
|
||||
const { mode, getBlock } = useDynamicBlocks();
|
||||
const { isActive, activate } = useActivation(props.block.blockId);
|
||||
const classes = computed(() => ({
|
||||
'sb-block': true,
|
||||
'sb-block_active': isActive.value,
|
||||
}));
|
||||
|
||||
const onChildUpdate = (updated: {[key: string]: any}) => {
|
||||
props.eventUpdate({
|
||||
...props.block,
|
||||
data: {
|
||||
...props.block.data,
|
||||
...updated,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const BlockComponent = getBlock(props.block.name) as any;
|
||||
if (mode.value === SbMode.Display) {
|
||||
return () => (
|
||||
|
@ -64,15 +69,33 @@ export default defineComponent({
|
|||
);
|
||||
}
|
||||
|
||||
return () => (<div class={classes.value}>
|
||||
const { triggerSizeCalculation } = useResizeObserver(el, BlockDimensions);
|
||||
watch(() => props.block.data, triggerSizeCalculation);
|
||||
|
||||
const onChildUpdate = (updated: {[key: string]: any}) => {
|
||||
props.eventUpdate({
|
||||
...props.block,
|
||||
data: {
|
||||
...props.block.data,
|
||||
...updated,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return () => <div
|
||||
ref={el}
|
||||
class={classes.value}
|
||||
onClick={($event: MouseEvent) => $event.stopPropagation()}
|
||||
>
|
||||
<div class="sb-block__edit-cover"></div>
|
||||
<div class="sb-block__remove">
|
||||
<SbButton>x</SbButton>
|
||||
</div>
|
||||
<div class="sb-block__sliders">
|
||||
<SbButton>></SbButton>
|
||||
<SbButton><</SbButton>
|
||||
</div>
|
||||
{props.sortable
|
||||
? <SbBlockOrdering
|
||||
eventMoveUp={props.eventMoveUp}
|
||||
eventMoveDown={props.eventMoveDown}
|
||||
eventRemoveBlock={props.eventRemoveBlock}
|
||||
sortable={props.sortable}
|
||||
/>
|
||||
: null}
|
||||
<BlockComponent
|
||||
data={props.block.data}
|
||||
block-id={props.block.blockId}
|
||||
|
@ -91,6 +114,6 @@ export default defineComponent({
|
|||
},
|
||||
}}
|
||||
/>
|
||||
</div>);
|
||||
</div>;
|
||||
},
|
||||
});
|
||||
|
|
5
src/components/internal/BlockOrdering.scss
Normal file
5
src/components/internal/BlockOrdering.scss
Normal file
|
@ -0,0 +1,5 @@
|
|||
.sb-block-ordering {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
flex-direction: column;
|
||||
}
|
65
src/components/internal/BlockOrdering.tsx
Normal file
65
src/components/internal/BlockOrdering.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import debounce from 'lodash-es/debounce';
|
||||
import {
|
||||
defineComponent,
|
||||
watch,
|
||||
reactive,
|
||||
computed,
|
||||
} from '@vue/composition-api';
|
||||
import {
|
||||
useBlockSizing,
|
||||
} from '@components/TreeElement';
|
||||
|
||||
import SbButton from './Button';
|
||||
|
||||
import './BlockOrdering.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'sb-block-ordering',
|
||||
|
||||
props: {
|
||||
sortable: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
eventRemoveBlock: { type: Function, default: () => {} },
|
||||
eventMoveUp: { type: Function, default: () => {} },
|
||||
eventMoveDown: { type: Function, default: () => {} },
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const styles = reactive({
|
||||
top: '',
|
||||
right: '',
|
||||
});
|
||||
|
||||
const classes = computed(() => ({
|
||||
'sb-block-ordering': true,
|
||||
[`sb-block-ordering_${props.sortable}`]: !!props.sortable,
|
||||
}));
|
||||
|
||||
const { editorDimensions, blockDimensions } = useBlockSizing();
|
||||
const resetStyles = debounce(() => {
|
||||
if (!editorDimensions.value || !blockDimensions.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const right = editorDimensions.value.width - blockDimensions.value.left;
|
||||
styles.top = `${blockDimensions.value.top}px`;
|
||||
styles.right = `${right}px`;
|
||||
});
|
||||
watch(editorDimensions, resetStyles);
|
||||
watch(blockDimensions, resetStyles);
|
||||
watch(() => props.sortable, resetStyles);
|
||||
|
||||
return () => (
|
||||
<div
|
||||
class={classes.value}
|
||||
style={styles}
|
||||
>
|
||||
<SbButton onClick={props.eventMoveUp}>{props.sortable === 'vertical' ? '↑' : '←'}</SbButton>
|
||||
<SbButton onClick={props.eventRemoveBlock}>x</SbButton>
|
||||
<SbButton onClick={props.eventMoveDown}>{props.sortable === 'vertical' ? '↓' : '→'}</SbButton>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1,8 +1,5 @@
|
|||
.sb-toolbar {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { defineComponent } from '@vue/composition-api';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import {
|
||||
defineComponent,
|
||||
watch,
|
||||
reactive,
|
||||
} from '@vue/composition-api';
|
||||
import { useBlockSizing } from '@components/TreeElement';
|
||||
|
||||
import './Toolbar.scss';
|
||||
|
||||
|
@ -6,8 +12,31 @@ export default defineComponent({
|
|||
name: 'sb-toolbar',
|
||||
|
||||
setup(props, context) {
|
||||
const styles = reactive({
|
||||
bottom: '',
|
||||
left: '',
|
||||
maxWidth: '',
|
||||
});
|
||||
|
||||
const { editorDimensions, blockDimensions } = useBlockSizing();
|
||||
const resetStyles = debounce(() => {
|
||||
if (!editorDimensions.value || !blockDimensions.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bottom = editorDimensions.value.height - blockDimensions.value.top;
|
||||
styles.bottom = `${bottom}px`;
|
||||
styles.left = `${blockDimensions.value.left}px`;
|
||||
styles.maxWidth = `${blockDimensions.value.width}px`;
|
||||
});
|
||||
watch(editorDimensions, resetStyles);
|
||||
watch(blockDimensions, resetStyles);
|
||||
|
||||
return () => (
|
||||
<div class="sb-toolbar">
|
||||
<div
|
||||
class="sb-toolbar"
|
||||
style={styles}
|
||||
>
|
||||
{context.slots.default()}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -110,6 +110,40 @@ export default defineComponent({
|
|||
activate(localData.children[newActiveIndex].blockId);
|
||||
};
|
||||
|
||||
const moveUp = (index: number) => {
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const curr = localData.children[index];
|
||||
const prev = localData.children[index - 1];
|
||||
localData.children = [
|
||||
...localData.children.slice(0, index - 1),
|
||||
curr,
|
||||
prev,
|
||||
...localData.children.slice(index + 1),
|
||||
];
|
||||
|
||||
props.eventUpdate({ children: [...localData.children] });
|
||||
};
|
||||
|
||||
const moveDown = (index: number) => {
|
||||
if (index === localData.children.length - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const curr = localData.children[index];
|
||||
const next = localData.children[index + 1];
|
||||
localData.children = [
|
||||
...localData.children.slice(0, index),
|
||||
next,
|
||||
curr,
|
||||
...localData.children.slice(index + 2),
|
||||
];
|
||||
|
||||
props.eventUpdate({ children: [...localData.children] });
|
||||
};
|
||||
|
||||
return () => (
|
||||
<div class={classes.value}>
|
||||
<SbToolbar slot="toolbar">
|
||||
|
@ -125,12 +159,17 @@ export default defineComponent({
|
|||
|
||||
{...localData.children.map((child, index) => (
|
||||
<SbBlock
|
||||
key={child.blockId}
|
||||
{...{ key: child.blockId }}
|
||||
data-order={index}
|
||||
block={child}
|
||||
eventUpdate={(updated: Block) => onChildUpdate(child, updated)}
|
||||
eventInsertBlock={(block: Block) => insertBlock(index, block)}
|
||||
eventAppendBlock={appendBlock}
|
||||
eventRemoveBlock={() => removeBlock(index)}
|
||||
eventMoveUp={() => moveUp(index)}
|
||||
eventMoveDown={() => moveDown(index)}
|
||||
sortable={localData.orientation}
|
||||
removable
|
||||
/>
|
||||
))}
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ export default defineComponent({
|
|||
activate(null);
|
||||
};
|
||||
|
||||
const onKeyup = ($event: KeyboardEvent) => {
|
||||
const onKeydown = ($event: KeyboardEvent) => {
|
||||
if ($event.key === 'Enter' && !$event.shiftKey) {
|
||||
const blockId = `${+(new Date())}`;
|
||||
props.eventInsertBlock({
|
||||
|
@ -129,7 +129,11 @@ export default defineComponent({
|
|||
activate(blockId);
|
||||
|
||||
$event.preventDefault();
|
||||
} else if ($event.key === 'Backspace' && localData.value === '') {
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyup = ($event: KeyboardEvent) => {
|
||||
if ($event.key === 'Backspace' && localData.value === '') {
|
||||
props.eventRemoveBlock();
|
||||
}
|
||||
};
|
||||
|
@ -153,6 +157,7 @@ export default defineComponent({
|
|||
onInput={onTextUpdate}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onKeydown={onKeydown}
|
||||
onKeyup={onKeyup}
|
||||
></p>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
* {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
@ -24,4 +26,5 @@ html {
|
|||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue