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==",
|
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
|
||||||
"dev": true
|
"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": {
|
"@types/minimatch": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||||
|
@ -9944,6 +9959,11 @@
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
|
||||||
"dev": true
|
"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": {
|
"lodash._arraycopy": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz",
|
||||||
|
@ -14887,9 +14907,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "3.8.3",
|
"version": "3.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
|
||||||
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
|
"integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
|
|
|
@ -12,10 +12,12 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/composition-api": "^0.5.0",
|
"@vue/composition-api": "^0.5.0",
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.4",
|
||||||
|
"lodash-es": "^4.17.15",
|
||||||
"vue": "^2.6.11"
|
"vue": "^2.6.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^24.0.19",
|
"@types/jest": "^24.0.19",
|
||||||
|
"@types/lodash-es": "^4.17.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||||
"@typescript-eslint/parser": "^2.26.0",
|
"@typescript-eslint/parser": "^2.26.0",
|
||||||
"@vue/cli-plugin-babel": "~4.3.0",
|
"@vue/cli-plugin-babel": "~4.3.0",
|
||||||
|
@ -35,7 +37,7 @@
|
||||||
"geckodriver": "^1.19.1",
|
"geckodriver": "^1.19.1",
|
||||||
"sass": "^1.26.3",
|
"sass": "^1.26.3",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"typescript": "~3.8.3",
|
"typescript": "~3.9.3",
|
||||||
"vue-template-compiler": "^2.6.11",
|
"vue-template-compiler": "^2.6.11",
|
||||||
"vuex": "^3.4.0"
|
"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 {
|
.sb-main {
|
||||||
padding: 50px 20px;
|
position: relative;
|
||||||
|
|
||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
|
padding: 50px 40px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import {
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
PropType,
|
PropType,
|
||||||
|
Ref,
|
||||||
|
watch,
|
||||||
} from '@vue/composition-api';
|
} from '@vue/composition-api';
|
||||||
import {
|
import {
|
||||||
model,
|
model,
|
||||||
|
@ -11,9 +13,11 @@ import {
|
||||||
Block,
|
Block,
|
||||||
SbMode,
|
SbMode,
|
||||||
Mode,
|
Mode,
|
||||||
|
EditorDimensions,
|
||||||
BlockDefinition,
|
BlockDefinition,
|
||||||
BlockLibraryDefinition,
|
BlockLibraryDefinition,
|
||||||
BlockLibrary,
|
BlockLibrary,
|
||||||
|
useResizeObserver,
|
||||||
} from '@components/TreeElement';
|
} from '@components/TreeElement';
|
||||||
|
|
||||||
import SbBlock from '@internal/Block';
|
import SbBlock from '@internal/Block';
|
||||||
|
@ -51,6 +55,9 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props: SchlechtenburgProps) {
|
setup(props: SchlechtenburgProps) {
|
||||||
|
const el: Ref<null|HTMLElement> = ref(null);
|
||||||
|
useResizeObserver(el, EditorDimensions);
|
||||||
|
|
||||||
const mode = ref(props.mode);
|
const mode = ref(props.mode);
|
||||||
provide(Mode, mode);
|
provide(Mode, mode);
|
||||||
|
|
||||||
|
@ -73,7 +80,10 @@ export default defineComponent({
|
||||||
provide(BlockLibrary, blockLibrary);
|
provide(BlockLibrary, blockLibrary);
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div class="sb-main">
|
<div
|
||||||
|
class="sb-main"
|
||||||
|
ref={el}
|
||||||
|
>
|
||||||
<SbBlock
|
<SbBlock
|
||||||
block={props.block}
|
block={props.block}
|
||||||
eventUpdate={props.eventUpdate}
|
eventUpdate={props.eventUpdate}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import {
|
||||||
inject,
|
inject,
|
||||||
reactive,
|
reactive,
|
||||||
computed,
|
computed,
|
||||||
|
watch,
|
||||||
|
provide,
|
||||||
} from '@vue/composition-api';
|
} from '@vue/composition-api';
|
||||||
|
|
||||||
export interface BlockDefinition {
|
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 const ActiveBlock = Symbol('Schlechtenburg active block');
|
||||||
export function useActivation(currentBlockId: string) {
|
export function useActivation(currentBlockId: string) {
|
||||||
const activeBlockId: Ref<string|null> = inject(ActiveBlock, ref(null));
|
const activeBlockId: Ref<string|null> = inject(ActiveBlock, ref(null));
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.sb-block {
|
.sb-block {
|
||||||
position: relative;
|
$block: &;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
justify-items: stretch;
|
justify-items: stretch;
|
||||||
|
@ -10,6 +11,14 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__edit-cover {
|
||||||
|
}
|
||||||
|
|
||||||
|
> .sb-block-ordering {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
&_active {
|
&_active {
|
||||||
outline: 1px solid var(--grey-2);
|
outline: 1px solid var(--grey-2);
|
||||||
|
|
||||||
|
@ -18,25 +27,10 @@
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
outline: 1px solid var(--grey-2);
|
outline: 1px solid var(--grey-2);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&__edit-cover {
|
> .sb-block-ordering {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
|
watch,
|
||||||
PropType,
|
PropType,
|
||||||
|
ref,
|
||||||
|
Ref,
|
||||||
} from '@vue/composition-api';
|
} from '@vue/composition-api';
|
||||||
import {
|
import {
|
||||||
Block,
|
Block,
|
||||||
useDynamicBlocks,
|
useDynamicBlocks,
|
||||||
useActivation,
|
useActivation,
|
||||||
SbMode,
|
SbMode,
|
||||||
|
BlockDimensions,
|
||||||
|
useResizeObserver,
|
||||||
} from '@components/TreeElement';
|
} from '@components/TreeElement';
|
||||||
|
|
||||||
import SbButton from './Button';
|
import SbBlockOrdering from './BlockOrdering';
|
||||||
|
|
||||||
import './Block.scss';
|
import './Block.scss';
|
||||||
|
|
||||||
|
@ -20,6 +25,9 @@ interface BlockProps {
|
||||||
eventInsertBlock: (b?: Block) => void;
|
eventInsertBlock: (b?: Block) => void;
|
||||||
eventAppendBlock: (b?: Block) => void;
|
eventAppendBlock: (b?: Block) => void;
|
||||||
eventRemoveBlock: () => void;
|
eventRemoveBlock: () => void;
|
||||||
|
eventMoveUp: () => void;
|
||||||
|
eventMoveDown: () => void;
|
||||||
|
sortable: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -30,30 +38,27 @@ export default defineComponent({
|
||||||
type: (null as unknown) as PropType<Block>,
|
type: (null as unknown) as PropType<Block>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
sortable: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
eventUpdate: { type: Function, default: () => {} },
|
eventUpdate: { type: Function, default: () => {} },
|
||||||
eventInsertBlock: { type: Function, default: () => {} },
|
eventInsertBlock: { type: Function, default: () => {} },
|
||||||
eventAppendBlock: { type: Function, default: () => {} },
|
eventAppendBlock: { type: Function, default: () => {} },
|
||||||
eventRemoveBlock: { type: Function, default: () => {} },
|
eventRemoveBlock: { type: Function, default: () => {} },
|
||||||
|
eventMoveUp: { type: Function, default: () => {} },
|
||||||
|
eventMoveDown: { type: Function, default: () => {} },
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props: BlockProps, context) {
|
setup(props: BlockProps, context) {
|
||||||
const { isActive, activate } = useActivation(props.block.blockId);
|
const el: Ref<null|HTMLElement> = ref(null);
|
||||||
const { mode, getBlock } = useDynamicBlocks();
|
const { mode, getBlock } = useDynamicBlocks();
|
||||||
|
const { isActive, activate } = useActivation(props.block.blockId);
|
||||||
const classes = computed(() => ({
|
const classes = computed(() => ({
|
||||||
'sb-block': true,
|
'sb-block': true,
|
||||||
'sb-block_active': isActive.value,
|
'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;
|
const BlockComponent = getBlock(props.block.name) as any;
|
||||||
if (mode.value === SbMode.Display) {
|
if (mode.value === SbMode.Display) {
|
||||||
return () => (
|
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__edit-cover"></div>
|
||||||
<div class="sb-block__remove">
|
{props.sortable
|
||||||
<SbButton>x</SbButton>
|
? <SbBlockOrdering
|
||||||
</div>
|
eventMoveUp={props.eventMoveUp}
|
||||||
<div class="sb-block__sliders">
|
eventMoveDown={props.eventMoveDown}
|
||||||
<SbButton>></SbButton>
|
eventRemoveBlock={props.eventRemoveBlock}
|
||||||
<SbButton><</SbButton>
|
sortable={props.sortable}
|
||||||
</div>
|
/>
|
||||||
|
: null}
|
||||||
<BlockComponent
|
<BlockComponent
|
||||||
data={props.block.data}
|
data={props.block.data}
|
||||||
block-id={props.block.blockId}
|
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 {
|
.sb-toolbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 100%;
|
|
||||||
left: 0;
|
|
||||||
width: auto;
|
width: auto;
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
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';
|
import './Toolbar.scss';
|
||||||
|
|
||||||
|
@ -6,8 +12,31 @@ export default defineComponent({
|
||||||
name: 'sb-toolbar',
|
name: 'sb-toolbar',
|
||||||
|
|
||||||
setup(props, context) {
|
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 () => (
|
return () => (
|
||||||
<div class="sb-toolbar">
|
<div
|
||||||
|
class="sb-toolbar"
|
||||||
|
style={styles}
|
||||||
|
>
|
||||||
{context.slots.default()}
|
{context.slots.default()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -110,6 +110,40 @@ export default defineComponent({
|
||||||
activate(localData.children[newActiveIndex].blockId);
|
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 () => (
|
return () => (
|
||||||
<div class={classes.value}>
|
<div class={classes.value}>
|
||||||
<SbToolbar slot="toolbar">
|
<SbToolbar slot="toolbar">
|
||||||
|
@ -125,12 +159,17 @@ export default defineComponent({
|
||||||
|
|
||||||
{...localData.children.map((child, index) => (
|
{...localData.children.map((child, index) => (
|
||||||
<SbBlock
|
<SbBlock
|
||||||
key={child.blockId}
|
{...{ key: child.blockId }}
|
||||||
|
data-order={index}
|
||||||
block={child}
|
block={child}
|
||||||
eventUpdate={(updated: Block) => onChildUpdate(child, updated)}
|
eventUpdate={(updated: Block) => onChildUpdate(child, updated)}
|
||||||
eventInsertBlock={(block: Block) => insertBlock(index, block)}
|
eventInsertBlock={(block: Block) => insertBlock(index, block)}
|
||||||
eventAppendBlock={appendBlock}
|
eventAppendBlock={appendBlock}
|
||||||
eventRemoveBlock={() => removeBlock(index)}
|
eventRemoveBlock={() => removeBlock(index)}
|
||||||
|
eventMoveUp={() => moveUp(index)}
|
||||||
|
eventMoveDown={() => moveDown(index)}
|
||||||
|
sortable={localData.orientation}
|
||||||
|
removable
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|
|
@ -117,7 +117,7 @@ export default defineComponent({
|
||||||
activate(null);
|
activate(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyup = ($event: KeyboardEvent) => {
|
const onKeydown = ($event: KeyboardEvent) => {
|
||||||
if ($event.key === 'Enter' && !$event.shiftKey) {
|
if ($event.key === 'Enter' && !$event.shiftKey) {
|
||||||
const blockId = `${+(new Date())}`;
|
const blockId = `${+(new Date())}`;
|
||||||
props.eventInsertBlock({
|
props.eventInsertBlock({
|
||||||
|
@ -129,7 +129,11 @@ export default defineComponent({
|
||||||
activate(blockId);
|
activate(blockId);
|
||||||
|
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
} else if ($event.key === 'Backspace' && localData.value === '') {
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyup = ($event: KeyboardEvent) => {
|
||||||
|
if ($event.key === 'Backspace' && localData.value === '') {
|
||||||
props.eventRemoveBlock();
|
props.eventRemoveBlock();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -153,6 +157,7 @@ export default defineComponent({
|
||||||
onInput={onTextUpdate}
|
onInput={onTextUpdate}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
onKeydown={onKeydown}
|
||||||
onKeyup={onKeyup}
|
onKeyup={onKeyup}
|
||||||
></p>
|
></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
* {
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,4 +26,5 @@ html {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue