schlechtenburg/packages/paragraph/lib/edit.tsx

191 lines
4.5 KiB
TypeScript
Raw Normal View History

2020-05-20 16:21:08 +02:00
import {
2020-12-30 02:32:46 +01:00
defineComponent,
2020-05-20 16:21:08 +02:00
reactive,
2020-05-24 22:39:14 +02:00
computed,
2020-05-20 16:21:08 +02:00
ref,
2020-05-24 17:33:25 +02:00
Ref,
2020-05-20 16:21:08 +02:00
onMounted,
2020-05-24 17:33:25 +02:00
watch,
2020-05-24 22:00:14 +02:00
PropType,
2020-12-27 22:32:43 +01:00
} from 'vue';
2020-05-20 16:21:08 +02:00
import {
model,
2020-12-30 14:34:23 +01:00
useActivation,
SbToolbar,
SbSelect,
2022-03-13 23:12:18 +01:00
generateBlockId,
2020-12-30 14:34:23 +01:00
} from '@schlechtenburg/core';
2022-09-05 21:12:20 +02:00
import { isEmptyContentEditable } from './contenteditable';
2020-05-24 22:00:14 +02:00
import {
getDefaultData,
2022-03-11 18:23:14 +01:00
IParagraphData,
2020-05-24 22:39:14 +02:00
} from './util';
2020-05-24 22:00:14 +02:00
import './style.scss';
2020-05-20 16:21:08 +02:00
2020-12-30 02:32:46 +01:00
export default defineComponent({
2020-05-24 22:00:14 +02:00
name: 'sb-paragraph-edit',
2020-05-20 16:21:08 +02:00
model,
props: {
2021-03-08 16:29:35 +01:00
blockId: { type: String, required: true },
2020-05-24 22:00:14 +02:00
data: {
2022-03-11 18:23:14 +01:00
type: (null as unknown) as PropType<IParagraphData>,
2020-05-24 22:00:14 +02:00
default: getDefaultData,
},
2022-09-05 21:12:20 +02:00
eventUpdate: {
2022-03-12 17:16:24 +01:00
type: (null as unknown) as PropType<((block?: Partial<IParagraphData>) => void)>,
default: () => {},
},
2022-09-05 21:12:20 +02:00
eventAppendBlock: {
2022-03-12 17:16:24 +01:00
type: (null as unknown) as PropType<((block?: any) => void)>,
default: () => {},
},
2022-09-05 21:12:20 +02:00
eventRemoveSelf: {
2022-03-12 17:16:24 +01:00
type: (null as unknown) as PropType<() => void>,
default: () => {},
},
2022-09-05 21:12:20 +02:00
eventActivateNext: {
2022-09-05 21:46:55 +02:00
type: (null as unknown) as PropType<(_i:number) => void>,
default: (_i:number) => {},
2022-03-12 17:16:24 +01:00
},
2022-09-05 21:12:20 +02:00
eventActivatePrevious: {
2022-09-05 21:46:55 +02:00
type: (null as unknown) as PropType<(_i:number) => void>,
default: (_i:number) => {},
2022-03-12 17:16:24 +01:00
},
2020-05-20 16:21:08 +02:00
},
2021-03-08 16:29:35 +01:00
setup(props) {
2020-05-25 23:10:21 +02:00
const localData = (reactive({
2020-05-24 17:33:25 +02:00
value: props.data.value,
2020-05-24 22:39:14 +02:00
align: props.data.align,
2020-05-24 17:33:25 +02:00
focused: false,
2020-05-27 17:06:14 +02:00
}) as unknown) as {
2020-05-25 23:10:21 +02:00
value: string;
align: string;
focused: boolean;
};
2020-05-24 22:39:14 +02:00
2020-05-24 17:33:25 +02:00
const inputEl: Ref<null|HTMLElement> = ref(null);
const { isActive, activate } = useActivation(props.blockId);
2020-05-27 20:36:46 +02:00
const focusInput = () => {
if (inputEl.value && isActive.value) {
inputEl.value.focus();
}
};
2020-05-24 17:33:25 +02:00
onMounted(() => {
2020-05-27 20:36:46 +02:00
focusInput();
2020-05-24 17:33:25 +02:00
if (inputEl.value) {
inputEl.value.innerHTML = localData.value;
}
});
2020-05-27 20:36:46 +02:00
watch(isActive, focusInput);
2020-05-24 17:33:25 +02:00
watch(() => props.data, () => {
localData.value = props.data.value;
2020-05-24 22:39:14 +02:00
localData.align = props.data.align;
2020-05-24 17:33:25 +02:00
if (inputEl.value) {
inputEl.value.innerHTML = localData.value;
}
});
2020-05-20 16:21:08 +02:00
2021-03-08 16:29:35 +01:00
const onTextUpdate = ($event: Event) => {
2020-05-25 23:10:21 +02:00
localData.value = ($event.target as HTMLElement).innerHTML;
2020-05-20 16:21:08 +02:00
};
2020-05-24 22:39:14 +02:00
const classes = computed(() => ({
2020-05-20 16:21:08 +02:00
'sb-paragraph': true,
2020-05-24 17:33:25 +02:00
'sb-paragraph_focused': localData.focused,
2020-05-24 22:39:14 +02:00
[`sb-paragraph_align-${localData.align}`]: true,
}));
2020-05-20 16:21:08 +02:00
2020-05-25 23:10:21 +02:00
const setAlignment = ($event: Event) => {
2022-09-05 21:12:20 +02:00
props.eventUpdate({
2020-05-27 17:06:14 +02:00
value: localData.value,
align: ($event.target as HTMLSelectElement).value,
});
2020-05-25 23:10:21 +02:00
};
2020-05-20 16:21:08 +02:00
const onFocus = () => {
2020-05-24 17:33:25 +02:00
localData.focused = true;
2020-05-27 17:32:35 +02:00
activate();
2020-05-20 16:21:08 +02:00
};
2020-05-24 17:33:25 +02:00
2020-05-20 16:21:08 +02:00
const onBlur = () => {
2020-05-24 17:33:25 +02:00
localData.focused = false;
2022-09-05 21:12:20 +02:00
props.eventUpdate({
2020-05-24 17:33:25 +02:00
value: localData.value,
2020-05-27 17:06:14 +02:00
align: localData.align,
2020-05-20 16:21:08 +02:00
});
};
2020-05-28 22:16:35 +02:00
const onKeydown = ($event: KeyboardEvent) => {
2021-02-22 19:13:37 +01:00
if ($event.key === 'Enter' && !$event.shiftKey) {
2022-03-13 23:12:18 +01:00
const id = generateBlockId();
2022-09-05 21:12:20 +02:00
props.eventAppendBlock({
2021-03-07 18:47:28 +01:00
id,
2020-05-24 17:33:25 +02:00
name: 'sb-paragraph',
2020-05-24 22:00:14 +02:00
data: getDefaultData(),
2020-05-24 17:33:25 +02:00
});
2020-05-20 16:21:08 +02:00
2021-03-07 18:47:28 +01:00
activate(id);
2020-05-24 17:33:25 +02:00
$event.preventDefault();
2020-05-28 22:16:35 +02:00
}
};
const onKeyup = ($event: KeyboardEvent) => {
2022-09-05 21:12:20 +02:00
if ($event.key === 'Backspace' && isEmptyContentEditable(localData.value)) {
props.eventRemoveSelf();
2021-02-22 19:13:37 +01:00
}
const selection = window.getSelection();
2021-03-08 16:29:35 +01:00
const node = selection?.focusNode;
const childNodes = Array.from(inputEl?.value?.childNodes || []);
const index = node ? childNodes.indexOf(node as ChildNode) : -1;
2021-02-22 19:13:37 +01:00
if (node === inputEl.value || index === 0 || index === childNodes.length -1) {
switch ($event.key) {
case 'ArrowDown':
2022-09-05 21:46:55 +02:00
props.eventActivateNext(index);
2021-02-22 19:13:37 +01:00
break;
case 'ArrowUp':
2022-09-05 21:46:55 +02:00
props.eventActivatePrevious(index);
2021-02-22 19:13:37 +01:00
break;
}
2020-05-24 17:33:25 +02:00
}
};
2020-05-20 16:21:08 +02:00
2020-05-25 23:10:21 +02:00
return () => (
<div class={classes.value}>
2020-05-24 22:39:14 +02:00
<SbToolbar>
2020-05-28 22:55:03 +02:00
<SbSelect
2021-03-08 16:29:35 +01:00
{...{
value: localData.align,
onChange: setAlignment,
}}
2020-05-25 23:10:21 +02:00
>
2020-05-24 22:39:14 +02:00
<option>left</option>
<option>center</option>
<option>right</option>
2020-05-28 22:55:03 +02:00
</SbSelect>
2020-05-24 22:39:14 +02:00
</SbToolbar>
2020-05-20 16:21:08 +02:00
<p
2020-05-25 23:10:21 +02:00
class="sb-paragraph__input"
ref={inputEl}
2020-05-20 16:21:08 +02:00
contenteditable
2020-05-27 17:06:14 +02:00
onInput={onTextUpdate}
onFocus={onFocus}
onBlur={onBlur}
2020-05-28 22:16:35 +02:00
onKeydown={onKeydown}
2020-05-27 20:36:46 +02:00
onKeyup={onKeyup}
2020-05-20 16:21:08 +02:00
></p>
2020-05-24 17:33:25 +02:00
</div>
2020-05-20 16:21:08 +02:00
);
},
2020-12-30 02:32:46 +01:00
});