schlechtenburg/packages/core/lib/components/Block.tsx

181 lines
4.6 KiB
TypeScript

import {
defineComponent,
computed,
watch,
PropType,
ref,
Ref,
} from 'vue';
import {
IBlockData,
OnUpdateBlockCb,
OnActivateNextCb,
OnRemoveSelfCb,
OnAppendBlockCb,
OnPrependBlockCb,
OnActivatePreviousCb,
} from '../types';
import { SbMode } from '../mode';
import { useResizeObserver, SymBlockDimensions } from '../use-resize-observer';
import { useActivation } from '../use-activation';
import { useBlockTree } from '../use-block-tree';
import { useDynamicBlocks } from '../use-dynamic-blocks';
import hoverCover from '../directives/hover-cover';
import SbMissingBlock from './MissingBlock';
import './Block.scss';
/**
* Displays a Schlechtenburg block either the mode of the schlechtenburg instance.
* You can use this to display child blocks inside your own blocks.
*/
export const SbBlock = defineComponent({
name: 'sb-block',
directives: {
hoverCover,
},
props: {
/**
* The state for the block.
*/
block: {
type: (null as unknown) as PropType<IBlockData<any>>,
required: true,
},
/**
* The state for the block.
*/
indexAsChild: {
type: Number,
default: -1,
},
/**
* Called when the block should be updated.
*/
eventUpdate: {
type: (null as unknown) as PropType<OnUpdateBlockCb>,
default: () => () => {},
},
/**
* Called when a sibling block should be inserted before the block
*/
eventPrependBlock: {
type: (null as unknown) as PropType<OnPrependBlockCb>,
default: () => () => {},
},
/**
* Called when a sibling block should be inserted after the block
*/
eventAppendBlock: {
type: (null as unknown) as PropType<OnAppendBlockCb>,
default: () => () => {},
},
/**
* Called when the block should be removed
*/
eventRemoveSelf: {
type: (null as unknown) as PropType<OnRemoveSelfCb>,
default: () => () => {},
},
/**
* Called when the previous sibling block should be activated
*/
eventActivatePrevious: {
type: (null as unknown) as PropType<OnActivatePreviousCb>,
default: () => () => {},
},
/**
* Called when the next sibling block should be activated
*/
eventActivateNext: {
type: (null as unknown) as PropType<OnActivateNextCb>,
default: () => () => {},
},
},
setup(props, context) {
const el: Ref<null|HTMLElement> = ref(null);
const { mode, getBlock } = useDynamicBlocks();
const {
isActive,
activate,
} = useActivation(props.block.id);
const classes = computed(() => ({
'sb-block': true,
'sb-block_active': isActive.value,
}));
const { triggerSizeCalculation } = useResizeObserver(el, SymBlockDimensions);
watch(() => props.block.data, triggerSizeCalculation);
const { register } = useBlockTree();
register(props.block, props.indexAsChild);
watch(props.block, () => { register(props.block, props.indexAsChild); });
const eventChildUpdate = (updated: {[key: string]: any}) => {
props.eventUpdate({
...props.block,
data: {
...props.block.data,
...updated,
},
});
};
return () => {
const BlockComponent = getBlock(props.block.name)?.[mode.value] as any;
if (!BlockComponent) {
const MissingBlock = SbMissingBlock[mode.value];
return <MissingBlock
name={props.block.name}
blockId={props.block.id}
/>;
}
if (mode.value === SbMode.View) {
return <BlockComponent
data={props.block.data}
blockId={props.block.id}
/>;
}
return <div
ref={el}
class={classes.value}
v-hover-cover
>
{
/**
* This is an alternative toolbar location that parent blocks can use to offer UI elements specific to child blocks.
* @slot context-toolbar
*/
context.slots['context-toolbar'] ? context.slots['context-toolbar']() : null
}
<BlockComponent
data={props.block.data}
blockId={props.block.id}
eventUpdate={eventChildUpdate}
eventPrependBlock={props.eventPrependBlock}
eventAppendBlock={props.eventAppendBlock}
eventRemoveSelf={props.eventRemoveSelf}
eventActivatePrevious={props.eventActivatePrevious}
eventActivateNext={props.eventActivateNext}
data-sb-block--content
{...{
onClick: ($event: MouseEvent) => {
$event.stopPropagation();
activate();
},
...context.attrs,
}}
/>
</div>;
};
},
});