Basic heading
This commit is contained in:
parent
01c2644a36
commit
4ffafd4bb5
|
@ -5,6 +5,8 @@
|
|||
align-items: stretch;
|
||||
justify-items: stretch;
|
||||
height: auto;
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
|
||||
> * > .sb-toolbar {
|
||||
opacity: 0;
|
||||
|
|
|
@ -25,7 +25,7 @@ export const SbBlockPicker = defineComponent({
|
|||
|
||||
const blockList = computed(() => Object.keys(customBlocks).map((key) => customBlocks[key]));
|
||||
|
||||
const selectBlock = (block: IBlockDefinition<any>) => () => {
|
||||
const selectBlock = (block: IBlockDefinition<any>) => {
|
||||
open.value = false;
|
||||
props.onPickedBlock({
|
||||
name: block.name,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
background-color: var(--grey-0);
|
||||
border: 1px solid var(--grey-2);
|
||||
position: relative;
|
||||
font-size: 1rem;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--interact);
|
||||
|
|
|
@ -4,6 +4,15 @@
|
|||
"data": {
|
||||
"orientation": "vertical",
|
||||
"children": [
|
||||
{
|
||||
"name": "sb-heading",
|
||||
"id": "1480592112212",
|
||||
"data": {
|
||||
"value": "A pretty heading",
|
||||
"align": "center",
|
||||
"level": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sb-paragraph",
|
||||
"id": "1590592112200",
|
||||
|
|
44
packages/heading/lib/display.ts
Normal file
44
packages/heading/lib/display.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import {
|
||||
defineComponent,
|
||||
computed,
|
||||
PropType,
|
||||
h,
|
||||
} from 'vue';
|
||||
import {
|
||||
model,
|
||||
} from '@schlechtenburg/core';
|
||||
import {
|
||||
getDefaultData,
|
||||
IHeadingData,
|
||||
} from './util';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'sb-heading-display',
|
||||
|
||||
model,
|
||||
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<IHeadingData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const classes = computed(() => ({
|
||||
'sb-heading': true,
|
||||
[`sb-heading_align-${props.data.align}`]: true,
|
||||
[`sb-heading_${props.data.level}`]: true,
|
||||
}));
|
||||
|
||||
return () => h(
|
||||
`h${props.data.level}`,
|
||||
{
|
||||
class: classes,
|
||||
innerHTML: props.data.value,
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1 +1,199 @@
|
|||
export default {};
|
||||
import {
|
||||
defineComponent,
|
||||
reactive,
|
||||
computed,
|
||||
ref,
|
||||
Ref,
|
||||
onMounted,
|
||||
watch,
|
||||
PropType,
|
||||
} from 'vue';
|
||||
import {
|
||||
model,
|
||||
useActivation,
|
||||
SbToolbar,
|
||||
SbSelect,
|
||||
} from '@schlechtenburg/core';
|
||||
import {
|
||||
getDefaultData,
|
||||
IHeadingData
|
||||
} from './util';
|
||||
import { getDefaultData as getDefaultParagraphData } from '@schlechtenburg/paragraph';
|
||||
|
||||
import './style.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'sb-heading-edit',
|
||||
|
||||
model,
|
||||
|
||||
props: {
|
||||
blockId: { type: String, required: true },
|
||||
data: {
|
||||
type: (null as unknown) as PropType<IHeadingData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
onUpdate: { type: Function, default: () => {} },
|
||||
onAppendBlock: { type: Function, default: () => {} },
|
||||
onRemoveSelf: { type: Function, default: () => {} },
|
||||
onActivateNext: { type: Function, default: () => {} },
|
||||
onActivatePrevious: { type: Function, default: () => {} },
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const localData = (reactive({
|
||||
value: props.data.value,
|
||||
align: props.data.align,
|
||||
level: props.data.level,
|
||||
focused: false,
|
||||
}) as unknown) as {
|
||||
value: string;
|
||||
align: string;
|
||||
level: number;
|
||||
focused: boolean;
|
||||
};
|
||||
|
||||
const inputEl: Ref<null|HTMLElement> = ref(null);
|
||||
|
||||
const { isActive, activate } = useActivation(props.blockId);
|
||||
|
||||
const focusInput = () => {
|
||||
if (inputEl.value && isActive.value) {
|
||||
inputEl.value.focus();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
focusInput();
|
||||
if (inputEl.value) {
|
||||
inputEl.value.innerHTML = localData.value;
|
||||
}
|
||||
});
|
||||
|
||||
watch(isActive, focusInput);
|
||||
|
||||
watch(() => props.data, () => {
|
||||
localData.value = props.data.value;
|
||||
localData.align = props.data.align;
|
||||
localData.level = props.data.level;
|
||||
if (inputEl.value) {
|
||||
inputEl.value.innerHTML = localData.value;
|
||||
}
|
||||
});
|
||||
|
||||
const onTextUpdate = ($event: Event) => {
|
||||
localData.value = ($event.target as HTMLElement).innerHTML;
|
||||
};
|
||||
|
||||
const classes = computed(() => ({
|
||||
'sb-heading': true,
|
||||
'sb-heading_focused': localData.focused,
|
||||
[`sb-heading_align${localData.align}`]: true,
|
||||
[`sb-heading_${localData.level}`]: true,
|
||||
}));
|
||||
|
||||
const setLevel = ($event: Event) => {
|
||||
props.onUpdate({
|
||||
...localData,
|
||||
level: parseInt(($event.target as HTMLSelectElement).value, 10),
|
||||
});
|
||||
};
|
||||
|
||||
const setAlignment = ($event: Event) => {
|
||||
props.onUpdate({
|
||||
...localData,
|
||||
align: ($event.target as HTMLSelectElement).value,
|
||||
});
|
||||
};
|
||||
|
||||
const onFocus = () => {
|
||||
localData.focused = true;
|
||||
activate();
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
localData.focused = false;
|
||||
props.onUpdate({
|
||||
value: localData.value,
|
||||
align: localData.align,
|
||||
level: localData.level,
|
||||
});
|
||||
};
|
||||
|
||||
const onKeydown = ($event: KeyboardEvent) => {
|
||||
if ($event.key === 'Enter' && !$event.shiftKey) {
|
||||
const id = `${+(new Date())}`;
|
||||
props.onAppendBlock({
|
||||
id,
|
||||
name: 'sb-paragraph',
|
||||
data: getDefaultParagraphData(),
|
||||
});
|
||||
|
||||
activate(id);
|
||||
|
||||
$event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyup = ($event: KeyboardEvent) => {
|
||||
if ($event.key === 'Backspace' && localData.value === '') {
|
||||
props.onRemoveSelf();
|
||||
}
|
||||
|
||||
const selection = window.getSelection();
|
||||
const node = selection?.focusNode;
|
||||
const childNodes = Array.from(inputEl?.value?.childNodes || []);
|
||||
const index = node ? childNodes.indexOf(node as ChildNode) : -1;
|
||||
if (node === inputEl.value || index === 0 || index === childNodes.length -1) {
|
||||
switch ($event.key) {
|
||||
case 'ArrowDown':
|
||||
props.onActivateNext();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
props.onActivatePrevious();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return () => (
|
||||
<div class={classes.value}>
|
||||
<SbToolbar>
|
||||
<SbSelect
|
||||
{...{
|
||||
value: localData.level,
|
||||
onChange: setLevel,
|
||||
}}
|
||||
>
|
||||
<option value={1}>h1</option>
|
||||
<option value={2}>h2</option>
|
||||
<option value={3}>h3</option>
|
||||
<option value={4}>h4</option>
|
||||
<option value={5}>h5</option>
|
||||
<option value={6}>h6</option>
|
||||
</SbSelect>
|
||||
<SbSelect
|
||||
{...{
|
||||
value: localData.align,
|
||||
onChange: setAlignment,
|
||||
}}
|
||||
>
|
||||
<option>left</option>
|
||||
<option>center</option>
|
||||
<option>right</option>
|
||||
</SbSelect>
|
||||
</SbToolbar>
|
||||
<p
|
||||
class="sb-heading__input"
|
||||
ref={inputEl}
|
||||
contenteditable
|
||||
onInput={onTextUpdate}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onKeydown={onKeydown}
|
||||
onKeyup={onKeyup}
|
||||
></p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -8,5 +8,5 @@ export default {
|
|||
name,
|
||||
getDefaultData,
|
||||
edit: defineAsyncComponent(() => import('./edit')),
|
||||
display: defineAsyncComponent(() => import('./edit')),
|
||||
display: defineAsyncComponent(() => import('./display')),
|
||||
};
|
||||
|
|
39
packages/heading/lib/style.scss
Normal file
39
packages/heading/lib/style.scss
Normal file
|
@ -0,0 +1,39 @@
|
|||
.sb-heading {
|
||||
flex-basis: 100%;
|
||||
font-weight: bold;
|
||||
|
||||
&_1 {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
&_2 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
&_3 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
&_4 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
&_5 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
&_6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: block;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
&_align {
|
||||
&-left { &, .sb-heading__input { text-align: left; } }
|
||||
&-right { &, .sb-heading__input { text-align: right; } }
|
||||
&-center { &, .sb-heading__input { text-align: center; } }
|
||||
}
|
||||
}
|
|
@ -1,2 +1,11 @@
|
|||
export const a = 1;
|
||||
export const getDefaultData = () => ({});
|
||||
export interface IHeadingData {
|
||||
value: string;
|
||||
align: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
export const getDefaultData: () => IHeadingData = () => ({
|
||||
value: '',
|
||||
align: 'left',
|
||||
level: 1,
|
||||
});
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@schlechtenburg/core": "^0.0.0",
|
||||
"@schlechtenburg/paragraph": "^0.0.0",
|
||||
"vue": "^3.0.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
} from '@schlechtenburg/core';
|
||||
import {
|
||||
getDefaultData,
|
||||
ImageData,
|
||||
IImageData,
|
||||
} from './util';
|
||||
|
||||
import './style.scss';
|
||||
|
@ -17,7 +17,7 @@ export default defineComponent({
|
|||
|
||||
props: {
|
||||
data: {
|
||||
type: (null as unknown) as PropType<ImageData>,
|
||||
type: (null as unknown) as PropType<IImageData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -13,10 +13,10 @@ import {
|
|||
SbBlock,
|
||||
IBlockData,
|
||||
} from '@schlechtenburg/core';
|
||||
import { ParagraphData } from '@schlechtenburg/paragraph';
|
||||
import { IParagraphData } from '@schlechtenburg/paragraph';
|
||||
import {
|
||||
getDefaultData,
|
||||
ImageData,
|
||||
IImageData,
|
||||
} from './util';
|
||||
|
||||
import './style.scss';
|
||||
|
@ -29,7 +29,7 @@ export default defineComponent({
|
|||
props: {
|
||||
onUpdate: { type: Function, default: () => {} },
|
||||
data: {
|
||||
type: (null as unknown) as PropType<ImageData>,
|
||||
type: (null as unknown) as PropType<IImageData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
},
|
||||
|
@ -75,7 +75,7 @@ export default defineComponent({
|
|||
}
|
||||
};
|
||||
|
||||
const onDescriptionUpdate = (description: IBlockData<ParagraphData>) => {
|
||||
const onDescriptionUpdate = (description: IBlockData<IParagraphData>) => {
|
||||
props.onUpdate({
|
||||
...props.data,
|
||||
description,
|
||||
|
@ -104,7 +104,7 @@ export default defineComponent({
|
|||
/>
|
||||
<SbBlock
|
||||
block={localData.description}
|
||||
onUpdate={(updated: IBlockData<ParagraphData>) => onDescriptionUpdate(updated)}
|
||||
onUpdate={(updated: IBlockData<IParagraphData>) => onDescriptionUpdate(updated)}
|
||||
/>
|
||||
</>
|
||||
: <SbButton {...{ onClick: selectImage }}>Select Image</SbButton>
|
||||
|
|
|
@ -4,17 +4,17 @@ import {
|
|||
} from '@schlechtenburg/core';
|
||||
import {
|
||||
name as paragraphName,
|
||||
ParagraphData,
|
||||
IParagraphData,
|
||||
getDefaultData as getDefaultParagraphData
|
||||
} from '@schlechtenburg/paragraph';
|
||||
|
||||
export interface ImageData {
|
||||
export interface IImageData {
|
||||
src: string;
|
||||
alt: string;
|
||||
description: IBlockData<ParagraphData>;
|
||||
description: IBlockData<IParagraphData>;
|
||||
}
|
||||
|
||||
export const getDefaultData: () => ImageData = () => ({
|
||||
export const getDefaultData: () => IImageData = () => ({
|
||||
src: '',
|
||||
alt: '',
|
||||
description: {
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
SbBlock,
|
||||
} from '@schlechtenburg/core';
|
||||
import {
|
||||
LayoutData,
|
||||
ILayoutData,
|
||||
getDefaultData,
|
||||
} from './util';
|
||||
|
||||
|
@ -21,7 +21,7 @@ export default defineComponent({
|
|||
|
||||
props: {
|
||||
data: {
|
||||
type: (null as unknown) as PropType<LayoutData>,
|
||||
type: (null as unknown) as PropType<ILayoutData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '@schlechtenburg/core';
|
||||
|
||||
import {
|
||||
LayoutData,
|
||||
ILayoutData,
|
||||
getDefaultData,
|
||||
} from './util';
|
||||
|
||||
|
@ -32,7 +32,7 @@ export default defineComponent({
|
|||
props: {
|
||||
onUpdate: { type: Function, default: () => {} },
|
||||
data: {
|
||||
type: (null as unknown) as PropType<LayoutData>,
|
||||
type: (null as unknown) as PropType<ILayoutData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ export default defineComponent({
|
|||
setup(props) {
|
||||
const { activate } = useActivation();
|
||||
|
||||
const localData: LayoutData = reactive({
|
||||
const localData: ILayoutData = reactive({
|
||||
orientation: props.data.orientation,
|
||||
children: [...props.data.children],
|
||||
});
|
||||
|
@ -79,6 +79,7 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
const appendBlock = (block: IBlockData<any>) => {
|
||||
console.log(appendBlock);
|
||||
localData.children = [
|
||||
...localData.children,
|
||||
block,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { IBlockData } from '@schlechtenburg/core';
|
||||
|
||||
export interface LayoutData {
|
||||
export interface ILayoutData {
|
||||
orientation: string;
|
||||
children: IBlockData<any>[];
|
||||
}
|
||||
|
||||
export const getDefaultData: () => LayoutData = () => ({
|
||||
export const getDefaultData: () => ILayoutData = () => ({
|
||||
orientation: 'vertical',
|
||||
children: [],
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from '@schlechtenburg/core';
|
||||
import {
|
||||
getDefaultData,
|
||||
ParagraphData,
|
||||
IParagraphData,
|
||||
} from './util';
|
||||
|
||||
import './style.scss';
|
||||
|
@ -20,7 +20,7 @@ export default defineComponent({
|
|||
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<ParagraphData>,
|
||||
type: Object as PropType<IParagraphData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from '@schlechtenburg/core';
|
||||
import {
|
||||
getDefaultData,
|
||||
ParagraphData,
|
||||
IParagraphData,
|
||||
} from './util';
|
||||
|
||||
import './style.scss';
|
||||
|
@ -29,7 +29,7 @@ export default defineComponent({
|
|||
props: {
|
||||
blockId: { type: String, required: true },
|
||||
data: {
|
||||
type: (null as unknown) as PropType<ParagraphData>,
|
||||
type: (null as unknown) as PropType<IParagraphData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
onUpdate: { type: Function, default: () => {} },
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
export interface ParagraphData {
|
||||
export interface IParagraphData {
|
||||
value: string;
|
||||
align: string;
|
||||
}
|
||||
|
||||
export const getDefaultData: () => ParagraphData = () => ({
|
||||
export const getDefaultData: () => IParagraphData = () => ({
|
||||
value: '',
|
||||
align: 'left',
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue