diff --git a/package-lock.json b/package-lock.json index c545f30..5fb0cd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index eaa991a..44b5326 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/ResizeObserver.d.ts b/src/ResizeObserver.d.ts new file mode 100644 index 0000000..aa36208 --- /dev/null +++ b/src/ResizeObserver.d.ts @@ -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; +} diff --git a/src/components/Schlechtenburg.scss b/src/components/Schlechtenburg.scss index 461e2fc..961d882 100644 --- a/src/components/Schlechtenburg.scss +++ b/src/components/Schlechtenburg.scss @@ -1,5 +1,5 @@ .sb-main { - padding: 50px 20px; - + position: relative; background-color: var(--bg); + padding: 50px 40px; } diff --git a/src/components/Schlechtenburg.tsx b/src/components/Schlechtenburg.tsx index 1601a06..5f44c46 100644 --- a/src/components/Schlechtenburg.tsx +++ b/src/components/Schlechtenburg.tsx @@ -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 = ref(null); + useResizeObserver(el, EditorDimensions); + const mode = ref(props.mode); provide(Mode, mode); @@ -73,7 +80,10 @@ export default defineComponent({ provide(BlockLibrary, blockLibrary); return () => ( -
+
, symbol: symbol) { + const dimensions: Ref = 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 = inject(EditorDimensions, ref(null)); + const blockDimensions: Ref = inject(BlockDimensions, ref(null)); + + return { editorDimensions, blockDimensions }; +} + export const ActiveBlock = Symbol('Schlechtenburg active block'); export function useActivation(currentBlockId: string) { const activeBlockId: Ref = inject(ActiveBlock, ref(null)); diff --git a/src/components/internal/Block.scss b/src/components/internal/Block.scss index dffebf8..e52ea56 100644 --- a/src/components/internal/Block.scss +++ b/src/components/internal/Block.scss @@ -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; + } } } diff --git a/src/components/internal/Block.tsx b/src/components/internal/Block.tsx index 7c5ac1b..e15996e 100644 --- a/src/components/internal/Block.tsx +++ b/src/components/internal/Block.tsx @@ -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, 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 = 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 () => (
+ 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 () =>
$event.stopPropagation()} + >
-
- x -
-
- > - < -
+ {props.sortable + ? + : null} -
); +
; }, }); diff --git a/src/components/internal/BlockOrdering.scss b/src/components/internal/BlockOrdering.scss new file mode 100644 index 0000000..8a6e639 --- /dev/null +++ b/src/components/internal/BlockOrdering.scss @@ -0,0 +1,5 @@ +.sb-block-ordering { + display: flex; + position: absolute; + flex-direction: column; +} diff --git a/src/components/internal/BlockOrdering.tsx b/src/components/internal/BlockOrdering.tsx new file mode 100644 index 0000000..311b2ea --- /dev/null +++ b/src/components/internal/BlockOrdering.tsx @@ -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 () => ( +
+ {props.sortable === 'vertical' ? '↑' : '←'} + x + {props.sortable === 'vertical' ? '↓' : '→'} +
+ ); + }, +}); diff --git a/src/components/internal/Toolbar.scss b/src/components/internal/Toolbar.scss index c840789..fd16d47 100644 --- a/src/components/internal/Toolbar.scss +++ b/src/components/internal/Toolbar.scss @@ -1,8 +1,5 @@ .sb-toolbar { position: absolute; - bottom: 100%; - left: 0; width: auto; - max-width: 100%; height: auto; } diff --git a/src/components/internal/Toolbar.tsx b/src/components/internal/Toolbar.tsx index a6d373c..73d361a 100644 --- a/src/components/internal/Toolbar.tsx +++ b/src/components/internal/Toolbar.tsx @@ -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 () => ( -
+
{context.slots.default()}
); diff --git a/src/components/user/Layout/edit.tsx b/src/components/user/Layout/edit.tsx index 34a573c..14e8220 100644 --- a/src/components/user/Layout/edit.tsx +++ b/src/components/user/Layout/edit.tsx @@ -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 () => (
@@ -125,12 +159,17 @@ export default defineComponent({ {...localData.children.map((child, index) => ( onChildUpdate(child, updated)} eventInsertBlock={(block: Block) => insertBlock(index, block)} eventAppendBlock={appendBlock} eventRemoveBlock={() => removeBlock(index)} + eventMoveUp={() => moveUp(index)} + eventMoveDown={() => moveDown(index)} + sortable={localData.orientation} + removable /> ))} diff --git a/src/components/user/Paragraph/edit.tsx b/src/components/user/Paragraph/edit.tsx index 11dfd49..074fd0b 100644 --- a/src/components/user/Paragraph/edit.tsx +++ b/src/components/user/Paragraph/edit.tsx @@ -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} >

diff --git a/src/main.scss b/src/main.scss index 1a25562..c6f8905 100644 --- a/src/main.scss +++ b/src/main.scss @@ -1,4 +1,6 @@ -* { +*, +*::before, +*::after { box-sizing: border-box; } @@ -24,4 +26,5 @@ html { body { margin: 0; + min-height: 100vh; }