schlechtenburg/packages/core/lib/inline/InlineToolbar.tsx

116 lines
2.9 KiB
TypeScript

import {
ref,
Ref,
onMounted,
onBeforeUnmount,
defineComponent,
computed,
} from 'vue';
import debounce from 'lodash/debounce';
import { useRootElement } from '../use-root-element';
import { useInline } from './use-inline';
import {
getSelection,
getAnchorElementForSelection,
getRangeFromSelection,
getRect,
} from './selection';
import './InlineToolbar.scss';
export const SbInlineToolbar = defineComponent({
name: 'sb-inlinetoolbar',
setup() {
const { rootElement } = useRootElement();
const {
clients,
getAllTools,
} = useInline();
const allTools = computed(() => getAllTools());
const selectionRect: Ref<DOMRect|null>= ref(null);
const updateSelectionRect = () => {
const selection = getSelection();
if (!selection) {
console.warn('Could not get selection');
return;
}
selectionRect.value = getRect(selection);
};
const showing: Ref<boolean> = ref(false);
const show = () => {
showing.value = true;
updateSelectionRect();
};
const hide = () => { showing.value = false; };
const style = computed(() => {
const rootRect = rootElement.value?.getBoundingClientRect();
const x = (selectionRect.value?.x || 0)
- (rootRect?.left || 0);
const y = (selectionRect.value?.y || 0)
+ (selectionRect.value?.height || 0)
- (rootRect?.top || 0);
return {
left: Math.floor(x) + 'px',
top: Math.floor(y) + 'px',
};
});
const classes = computed(() => ({
'sb-inline-toolbar': true,
'sb-inline-toolbar_hidden': !showing.value,
}));
const onSelectionChanged = debounce(() => {
const selection = getSelection();
if (!selection) {
console.warn('Could not get selection');
return
}
const range = getRangeFromSelection(selection);
// If we're not selecting anything, bail
if (!range || range.endOffset === range.startOffset) {
hide();
return;
}
const focusedElement = getAnchorElementForSelection(selection);
if (!focusedElement) {
hide();
return;
}
// If new selection is not in registered clients, close it
if (!clients.value.find(client => client === focusedElement)) {
hide();
return;
}
show();
}, 50);
onMounted(() => {
rootElement.value?.addEventListener('click', close);
document.addEventListener('selectionchange', onSelectionChanged, true);
});
onBeforeUnmount(() => {
rootElement.value?.removeEventListener('click', close);
document.removeEventListener('selectionchange', onSelectionChanged);
});
return () => allTools.value.length
? <div
style={style.value}
class={classes.value}
>{allTools.value.map(tool => tool.ui)}</div>
: null;
},
});