@zipify/wysiwyg 4.1.0-0 → 4.1.0-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/build/settings.js +1 -1
- package/config/jest/setupTests.js +3 -0
- package/dist/cli.js +2 -3
- package/dist/wysiwyg.css +37 -37
- package/dist/wysiwyg.mjs +920 -846
- package/example/ai-component/AiComponent.vue +16 -50
- package/lib/Wysiwyg.vue +9 -17
- package/lib/components/base/Modal.vue +19 -18
- package/lib/components/base/ModalFloating.vue +32 -0
- package/lib/components/base/__tests__/Modal.test.js +2 -19
- package/lib/components/base/composables/useModalToggler.js +3 -34
- package/lib/components/base/dropdown/Dropdown.vue +7 -6
- package/lib/components/base/dropdown/DropdownActivator.vue +4 -5
- package/lib/components/base/dropdown/__tests__/DropdownActivator.test.js +1 -1
- package/lib/components/base/dropdown/__tests__/DropdownOption.test.js +1 -1
- package/lib/components/toolbar/Toolbar.vue +29 -13
- package/lib/components/toolbar/ToolbarFloating.vue +42 -0
- package/lib/components/toolbar/__tests__/Toolbar.test.js +4 -6
- package/lib/components/toolbar/controls/LineHeightControl.vue +9 -7
- package/lib/components/toolbar/controls/__tests__/LineHeightControl.test.js +2 -4
- package/lib/components/toolbar/controls/link/LinkControl.vue +13 -14
- package/lib/components/toolbar/controls/link/destination/LinkControlDestination.vue +1 -1
- package/lib/components/toolbar/controls/link/destination/LinkControlPageBlock.vue +1 -1
- package/lib/composables/__tests__/useEditor.test.js +26 -5
- package/lib/composables/index.js +0 -1
- package/lib/composables/useEditor.js +7 -2
- package/lib/styles/content.css +0 -1
- package/package.json +50 -51
- package/lib/components/base/composables/__tests__/useModalToggler.test.js +0 -59
- package/lib/composables/useToolbar.js +0 -36
- /package/config/build/{example.config.js → example.config.mjs} +0 -0
- /package/config/build/{lib.config.js → lib.config.mjs} +0 -0
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="wrapperRef">
|
|
3
|
-
<Button icon skin="toolbar" :active="
|
|
3
|
+
<Button icon skin="toolbar" :active="toggler.isOpened" @click="toggler.open">
|
|
4
4
|
<Icon name="sparkles" size="28px" />
|
|
5
5
|
<span class="zw-ai-control__caption">AI</span>
|
|
6
6
|
</Button>
|
|
7
7
|
|
|
8
|
-
<Modal
|
|
8
|
+
<Modal
|
|
9
|
+
class="zw-ai-component__modal"
|
|
10
|
+
:toggler="toggler"
|
|
11
|
+
:reference-ref="wrapperRef"
|
|
12
|
+
focus-first-control
|
|
13
|
+
>
|
|
9
14
|
<p>Modal content</p>
|
|
10
15
|
|
|
11
16
|
<Button skin="primary" @click="onInsert">
|
|
@@ -15,57 +20,18 @@
|
|
|
15
20
|
</div>
|
|
16
21
|
</template>
|
|
17
22
|
|
|
18
|
-
<script>
|
|
19
|
-
import { inject, ref
|
|
23
|
+
<script setup>
|
|
24
|
+
import { inject, ref } from 'vue';
|
|
20
25
|
import { Button, Icon, useModalToggler, Modal } from '../../lib/components/base';
|
|
21
26
|
import { InjectionTokens } from '../../lib/injectionTokens';
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
const editor = inject(InjectionTokens.EDITOR);
|
|
29
|
+
const wrapperRef = ref(null);
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
Modal,
|
|
28
|
-
Button,
|
|
29
|
-
Icon
|
|
30
|
-
},
|
|
31
|
+
const toggler = useModalToggler();
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const onBeforeOpened = () => {};
|
|
38
|
-
|
|
39
|
-
const toggler = useModalToggler({
|
|
40
|
-
options: {
|
|
41
|
-
placement: 'bottom-start',
|
|
42
|
-
strategy: 'absolute',
|
|
43
|
-
offset: [-8, 5]
|
|
44
|
-
},
|
|
45
|
-
onBeforeOpened: () => onBeforeOpened(),
|
|
46
|
-
wrapperRef,
|
|
47
|
-
modalRef
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const onInsert = () => {
|
|
51
|
-
editor.chain()
|
|
52
|
-
.focus()
|
|
53
|
-
.insertContent('Hello from AI component')
|
|
54
|
-
.setDocMeta('ai_generated', true)
|
|
55
|
-
.run();
|
|
56
|
-
toggler.close();
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const isActive = computed(() => unref(toggler.isOpened));
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
editor,
|
|
63
|
-
wrapperRef,
|
|
64
|
-
modalRef,
|
|
65
|
-
toggler,
|
|
66
|
-
onInsert,
|
|
67
|
-
isActive
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
};
|
|
33
|
+
function onInsert() {
|
|
34
|
+
editor.chain().focus().insertContent('Hello from AI component').run();
|
|
35
|
+
toggler.close();
|
|
36
|
+
}
|
|
71
37
|
</script>
|
package/lib/Wysiwyg.vue
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="zw-wysiwyg" ref="wysiwygRef">
|
|
3
3
|
<Toolbar
|
|
4
|
-
:toolbar="toolbar"
|
|
5
4
|
:device="device"
|
|
5
|
+
:visible="isToolbarVisible"
|
|
6
6
|
:popup-mode="popupMode"
|
|
7
|
+
:reference-ref="wysiwygRef"
|
|
8
|
+
:placement="toolbarPlacement"
|
|
9
|
+
:offsets="toolbarOffsets"
|
|
7
10
|
:ai-component="aiComponent"
|
|
8
|
-
ref="toolbarRef"
|
|
9
11
|
/>
|
|
10
12
|
|
|
11
13
|
<EditorContent :editor="editor" />
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
import { EditorContent } from '@tiptap/vue-3';
|
|
17
19
|
import { provide, toRef, ref, computed } from 'vue';
|
|
18
20
|
import { Toolbar } from './components';
|
|
19
|
-
import {
|
|
21
|
+
import { useEditor } from './composables';
|
|
20
22
|
import { buildExtensions } from './extensions';
|
|
21
23
|
import { InjectionTokens } from './injectionTokens';
|
|
22
24
|
import { ContextWindow, FavoriteColors, Storage } from './services';
|
|
@@ -137,27 +139,17 @@ const MAX_FONT_SIZE = 112;
|
|
|
137
139
|
ContextWindow.use(props.window);
|
|
138
140
|
|
|
139
141
|
const fonts = props.fonts.map((font) => new Font(font));
|
|
140
|
-
const toolbarRef = ref(null);
|
|
141
142
|
const wysiwygRef = ref(null);
|
|
142
|
-
const wrapperRef = computed(() => wysiwygRef.value
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
const toolbar = useToolbar({
|
|
146
|
-
wrapperRef: wysiwygRef,
|
|
147
|
-
placementRef: toRef(props, 'toolbarPlacement'),
|
|
148
|
-
isActiveRef: isToolbarActiveRef,
|
|
149
|
-
offsets: props.toolbarOffsets
|
|
150
|
-
});
|
|
143
|
+
const wrapperRef = computed(() => wysiwygRef.value || document.body);
|
|
144
|
+
const isToolbarVisible = computed(() => props.active && !props.readonly);
|
|
151
145
|
|
|
152
|
-
|
|
153
|
-
emit('update:model-value', content);
|
|
154
|
-
}
|
|
146
|
+
const onChange = (content) => emit('update:model-value', content);
|
|
155
147
|
|
|
156
148
|
const pageBlocks = toRef(props, 'pageBlocks');
|
|
157
149
|
|
|
158
150
|
const { editor, getContent } = useEditor({
|
|
159
151
|
content: toRef(props, 'modelValue'),
|
|
160
|
-
onChange
|
|
152
|
+
onChange,
|
|
161
153
|
isReadonlyRef: toRef(props, 'readonly'),
|
|
162
154
|
|
|
163
155
|
extensions: buildExtensions({
|
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
2
|
+
<Transition name="zw-modal-" :duration="transitionDuration">
|
|
3
|
+
<ModalFloating
|
|
4
4
|
class="zw-modal"
|
|
5
5
|
ref="hostRef"
|
|
6
6
|
tabindex="-1"
|
|
7
|
-
:
|
|
8
|
-
v-if="isOpened"
|
|
7
|
+
:reference-ref="referenceRef"
|
|
9
8
|
v-out-click="toggler.close"
|
|
9
|
+
v-if="toggler.isOpened && referenceRef"
|
|
10
10
|
data-test-selector="modal"
|
|
11
11
|
>
|
|
12
12
|
<slot />
|
|
13
|
-
</
|
|
14
|
-
</
|
|
13
|
+
</ModalFloating>
|
|
14
|
+
</Transition>
|
|
15
15
|
</template>
|
|
16
16
|
|
|
17
17
|
<script setup>
|
|
18
|
-
import {
|
|
18
|
+
import { nextTick, ref, toRef, watch } from 'vue';
|
|
19
19
|
import { outClick as vOutClick } from '../../directives';
|
|
20
20
|
import { useDeselectionLock, useElementRef, useModalToggler } from './composables';
|
|
21
|
+
import ModalFloating from './ModalFloating';
|
|
21
22
|
|
|
22
23
|
const props = defineProps({
|
|
24
|
+
referenceRef: {
|
|
25
|
+
type: Object,
|
|
26
|
+
required: false,
|
|
27
|
+
default: null
|
|
28
|
+
},
|
|
29
|
+
|
|
23
30
|
toggler: {
|
|
24
31
|
type: Object,
|
|
25
32
|
required: false,
|
|
@@ -45,28 +52,22 @@ const props = defineProps({
|
|
|
45
52
|
}
|
|
46
53
|
});
|
|
47
54
|
|
|
48
|
-
const
|
|
55
|
+
const transitionDuration = {
|
|
49
56
|
enter: 200,
|
|
50
57
|
leave: 100
|
|
51
58
|
};
|
|
52
59
|
|
|
53
60
|
const toggler = props.toggler || useModalToggler();
|
|
54
|
-
const isOpened = computed(() => unref(toggler.isOpened));
|
|
55
61
|
const hostRef = ref(null);
|
|
56
62
|
const hostEl = useElementRef(hostRef);
|
|
57
63
|
|
|
58
|
-
const modalStyles = computed(() => ({
|
|
59
|
-
'--zw-modal-max-height': `${props.maxHeight}px`,
|
|
60
|
-
'--zw-modal-max-width': `${props.maxWidth}px`
|
|
61
|
-
}));
|
|
62
|
-
|
|
63
64
|
useDeselectionLock({
|
|
64
|
-
isActiveRef: toggler
|
|
65
|
+
isActiveRef: toRef(toggler, 'isOpened'),
|
|
65
66
|
hostRef
|
|
66
67
|
});
|
|
67
68
|
|
|
68
69
|
if (props.focusFirstControl) {
|
|
69
|
-
watch(toggler
|
|
70
|
+
watch(toRef(toggler, 'isOpened'), async (_, wasOpened) => {
|
|
70
71
|
if (wasOpened) return;
|
|
71
72
|
|
|
72
73
|
await nextTick();
|
|
@@ -82,8 +83,8 @@ if (props.focusFirstControl) {
|
|
|
82
83
|
border-radius: 2px;
|
|
83
84
|
box-shadow: 0 0 4px rgba(var(--zw-color-black), 0.3);
|
|
84
85
|
background-color: rgb(var(--zw-color-n15));
|
|
85
|
-
max-height:
|
|
86
|
-
max-width:
|
|
86
|
+
max-height: v-bind("maxHeight + 'px'");
|
|
87
|
+
max-width: v-bind("maxWidth + 'px'");
|
|
87
88
|
z-index: 1000;
|
|
88
89
|
position: fixed;
|
|
89
90
|
will-change: transform;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="floatingRef" :style="floatingStyles">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import { ref, toRef } from 'vue';
|
|
9
|
+
import { useFloating, limitShift, offset, shift, autoUpdate } from '@floating-ui/vue';
|
|
10
|
+
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
referenceRef: {
|
|
13
|
+
type: Object,
|
|
14
|
+
required: true
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const floatingRef = ref(null);
|
|
19
|
+
const referenceRef = toRef(props, 'referenceRef');
|
|
20
|
+
|
|
21
|
+
const { floatingStyles } = useFloating(referenceRef, floatingRef, {
|
|
22
|
+
placement: 'bottom',
|
|
23
|
+
strategy: 'fixed',
|
|
24
|
+
|
|
25
|
+
middleware: [
|
|
26
|
+
shift({ padding: 16, crossAxis: true, limiter: limitShift() }),
|
|
27
|
+
offset({ mainAxis: 4 })
|
|
28
|
+
],
|
|
29
|
+
|
|
30
|
+
whileElementsMounted: autoUpdate
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
@@ -22,7 +22,7 @@ function createComponent({ toggler, maxHeight, maxWidth } = {}) {
|
|
|
22
22
|
return shallowMount(Modal, {
|
|
23
23
|
global: {
|
|
24
24
|
stubs: {
|
|
25
|
-
|
|
25
|
+
Transition: {
|
|
26
26
|
render() {
|
|
27
27
|
return this.$slots.default?.[0]?.();
|
|
28
28
|
}
|
|
@@ -32,6 +32,7 @@ function createComponent({ toggler, maxHeight, maxWidth } = {}) {
|
|
|
32
32
|
},
|
|
33
33
|
props: {
|
|
34
34
|
toggler: toggler ?? createToggler(true),
|
|
35
|
+
referenceRef: document.createElement('div'),
|
|
35
36
|
maxHeight: maxHeight ?? 1000,
|
|
36
37
|
maxWidth: maxWidth ?? 1000
|
|
37
38
|
}
|
|
@@ -63,21 +64,3 @@ describe('open/close', () => {
|
|
|
63
64
|
expect(wrapper).toVueContainElement(SELECTORS.MODAL);
|
|
64
65
|
});
|
|
65
66
|
});
|
|
66
|
-
|
|
67
|
-
describe('rendering', () => {
|
|
68
|
-
test('should render max width', () => {
|
|
69
|
-
const wrapper = createComponent({ maxWidth: 256 });
|
|
70
|
-
const modalWrapper = wrapper.find(SELECTORS.MODAL);
|
|
71
|
-
const maxWidth = modalWrapper.element.style.getPropertyValue('--zw-modal-max-width');
|
|
72
|
-
|
|
73
|
-
expect(maxWidth).toBe('256px');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('should render max height', () => {
|
|
77
|
-
const wrapper = createComponent({ maxHeight: 256 });
|
|
78
|
-
const modalWrapper = wrapper.find(SELECTORS.MODAL);
|
|
79
|
-
const maxHeight = modalWrapper.element.style.getPropertyValue('--zw-modal-max-height');
|
|
80
|
-
|
|
81
|
-
expect(maxHeight).toBe('256px');
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,35 +1,9 @@
|
|
|
1
|
-
import { inject,
|
|
2
|
-
import { autoUpdate, computePosition, limitShift, offset, shift } from '@floating-ui/dom';
|
|
1
|
+
import { inject, reactive, ref } from 'vue';
|
|
3
2
|
import { InjectionTokens } from '../../../injectionTokens';
|
|
4
|
-
import { useElementRef } from './useElementRef';
|
|
5
3
|
|
|
6
|
-
export function useModalToggler({ onBeforeOpened, onClosed
|
|
4
|
+
export function useModalToggler({ onBeforeOpened, onClosed } = {}) {
|
|
7
5
|
const editor = inject(InjectionTokens.EDITOR);
|
|
8
6
|
const isOpened = ref(false);
|
|
9
|
-
let floatingInstance;
|
|
10
|
-
|
|
11
|
-
function initModal() {
|
|
12
|
-
const wrapperEl = useElementRef(wrapperRef);
|
|
13
|
-
const modalEl = useElementRef(modalRef);
|
|
14
|
-
|
|
15
|
-
floatingInstance = autoUpdate(wrapperEl.value, modalEl.value, async () => {
|
|
16
|
-
const positioning = await computePosition(wrapperEl.value, modalEl.value, {
|
|
17
|
-
placement: 'bottom',
|
|
18
|
-
strategy: 'fixed',
|
|
19
|
-
middleware: [
|
|
20
|
-
shift({ padding: 16, crossAxis: true, limiter: limitShift() }),
|
|
21
|
-
offset({ mainAxis: 4 })
|
|
22
|
-
]
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const { x, y } = positioning;
|
|
26
|
-
|
|
27
|
-
Object.assign(modalEl.value, {
|
|
28
|
-
left: `${x}px`,
|
|
29
|
-
top: `${y}px`
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
7
|
|
|
34
8
|
async function open() {
|
|
35
9
|
if (isOpened.value) return;
|
|
@@ -37,20 +11,15 @@ export function useModalToggler({ onBeforeOpened, onClosed, wrapperRef, modalRef
|
|
|
37
11
|
onBeforeOpened?.();
|
|
38
12
|
editor.commands.storeSelection();
|
|
39
13
|
isOpened.value = true;
|
|
40
|
-
|
|
41
|
-
await nextTick();
|
|
42
|
-
|
|
43
|
-
initModal();
|
|
44
14
|
}
|
|
45
15
|
|
|
46
16
|
function close() {
|
|
47
17
|
isOpened.value = false;
|
|
48
|
-
floatingInstance?.();
|
|
49
18
|
editor.commands.restoreSelection();
|
|
50
19
|
onClosed?.();
|
|
51
20
|
}
|
|
52
21
|
|
|
53
22
|
const toggle = (toOpen) => toOpen ? open() : close();
|
|
54
23
|
|
|
55
|
-
return { isOpened, open, close, toggle };
|
|
24
|
+
return reactive({ isOpened, open, close, toggle });
|
|
56
25
|
}
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
</template>
|
|
7
7
|
</DropdownActivator>
|
|
8
8
|
|
|
9
|
-
<Modal
|
|
9
|
+
<Modal
|
|
10
|
+
max-height="300"
|
|
11
|
+
:max-width="maxWidth"
|
|
12
|
+
:toggler="toggler"
|
|
13
|
+
:reference-ref="dropdownRef"
|
|
14
|
+
>
|
|
10
15
|
<DropdownMenu :options="options">
|
|
11
16
|
<template #option="attrs">
|
|
12
17
|
<slot name="option" v-bind="attrs" />
|
|
@@ -58,7 +63,6 @@ const props = defineProps({
|
|
|
58
63
|
const emit = defineEmits(['change']);
|
|
59
64
|
|
|
60
65
|
const dropdownRef = ref(null);
|
|
61
|
-
const modalRef = ref(null);
|
|
62
66
|
|
|
63
67
|
const activeOptionManager = useActiveOptionManager({
|
|
64
68
|
optionsRef: toRef(props, 'options'),
|
|
@@ -67,10 +71,7 @@ const activeOptionManager = useActiveOptionManager({
|
|
|
67
71
|
onChange: (value) => emit('change', value.id)
|
|
68
72
|
});
|
|
69
73
|
|
|
70
|
-
const toggler = useModalToggler(
|
|
71
|
-
wrapperRef: dropdownRef,
|
|
72
|
-
modalRef
|
|
73
|
-
});
|
|
74
|
+
const toggler = useModalToggler();
|
|
74
75
|
|
|
75
76
|
provide(InjectionTokens.ACTIVE_MANAGER, activeOptionManager);
|
|
76
77
|
provide(InjectionTokens.TOGGLER, toggler);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<slot :open="dropdownToggler.open" :isOpened="isOpened">
|
|
2
|
+
<slot :open="dropdownToggler.open" :isOpened="dropdownToggler.isOpened">
|
|
3
3
|
<Button
|
|
4
4
|
skin="toolbar"
|
|
5
5
|
class="zw-dropdown__activator"
|
|
6
6
|
:class="dropdownClasses"
|
|
7
|
-
:active="isOpened"
|
|
7
|
+
:active="dropdownToggler.isOpened"
|
|
8
8
|
@click="dropdownToggler.open"
|
|
9
9
|
>
|
|
10
10
|
<span class="zw-dropdown__activator-title zw-text--truncate">
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
</template>
|
|
30
30
|
|
|
31
31
|
<script setup>
|
|
32
|
-
import { computed, inject, toRef
|
|
32
|
+
import { computed, inject, toRef } from 'vue';
|
|
33
33
|
import { tooltip as vTooltip } from '../../../directives';
|
|
34
34
|
import Button from '../Button';
|
|
35
35
|
import Icon from '../Icon';
|
|
@@ -52,13 +52,12 @@ const props = defineProps({
|
|
|
52
52
|
|
|
53
53
|
const activeOptionManager = inject(InjectionTokens.ACTIVE_MANAGER);
|
|
54
54
|
const dropdownToggler = inject(InjectionTokens.TOGGLER);
|
|
55
|
-
const isOpened = computed(() => unref(dropdownToggler.isOpened));
|
|
56
55
|
const color = toRef(props, 'color');
|
|
57
56
|
|
|
58
57
|
const activeOptionTitle = useDropdownEntityTitle(activeOptionManager.activeOption);
|
|
59
58
|
|
|
60
59
|
const dropdownClasses = computed(() => ({
|
|
61
|
-
'zw-dropdown__activator--active': dropdownToggler.isOpened
|
|
60
|
+
'zw-dropdown__activator--active': dropdownToggler.isOpened,
|
|
62
61
|
'zw-dropdown__activator--gray': color.value === 'gray'
|
|
63
62
|
}));
|
|
64
63
|
</script>
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<keep-alive>
|
|
3
3
|
<transition name="zw-toolbar-" duration="150">
|
|
4
|
-
<
|
|
4
|
+
<ToolbarFloating
|
|
5
|
+
class="zw-toolbar"
|
|
6
|
+
:reference-ref="referenceRef"
|
|
7
|
+
:placement="placement"
|
|
8
|
+
:offsets="offsets"
|
|
9
|
+
v-if="visible && referenceRef"
|
|
10
|
+
>
|
|
5
11
|
<component :is="layoutComponent" :ai-component="aiComponent" />
|
|
6
|
-
</
|
|
12
|
+
</ToolbarFloating>
|
|
7
13
|
</transition>
|
|
8
14
|
</keep-alive>
|
|
9
15
|
</template>
|
|
10
16
|
|
|
11
17
|
<script setup>
|
|
12
|
-
import { computed
|
|
18
|
+
import { computed } from 'vue';
|
|
13
19
|
import { Devices } from '../../enums';
|
|
14
20
|
import { ToolbarDesktop, ToolbarMobile, ToolbarPopup } from './layouts';
|
|
21
|
+
import ToolbarFloating from './ToolbarFloating';
|
|
15
22
|
|
|
16
23
|
const props = defineProps({
|
|
17
24
|
device: {
|
|
@@ -19,8 +26,8 @@ const props = defineProps({
|
|
|
19
26
|
required: true
|
|
20
27
|
},
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
type:
|
|
29
|
+
visible: {
|
|
30
|
+
type: Boolean,
|
|
24
31
|
required: true
|
|
25
32
|
},
|
|
26
33
|
|
|
@@ -29,6 +36,22 @@ const props = defineProps({
|
|
|
29
36
|
required: true
|
|
30
37
|
},
|
|
31
38
|
|
|
39
|
+
referenceRef: {
|
|
40
|
+
type: Object,
|
|
41
|
+
required: false,
|
|
42
|
+
default: null
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
placement: {
|
|
46
|
+
type: String,
|
|
47
|
+
required: true
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
offsets: {
|
|
51
|
+
type: Array,
|
|
52
|
+
required: true
|
|
53
|
+
},
|
|
54
|
+
|
|
32
55
|
aiComponent: {
|
|
33
56
|
type: Object,
|
|
34
57
|
required: false,
|
|
@@ -41,13 +64,6 @@ const layoutComponent = computed(() => {
|
|
|
41
64
|
|
|
42
65
|
return props.device === Devices.MOBILE ? ToolbarMobile : ToolbarDesktop;
|
|
43
66
|
});
|
|
44
|
-
|
|
45
|
-
const isVisible = computed(() => props.toolbar.isActiveRef.value);
|
|
46
|
-
const toolbarRef = ref(null);
|
|
47
|
-
|
|
48
|
-
watch(toolbarRef, (toolbarEl) => {
|
|
49
|
-
toolbarEl && props.toolbar.mount(toolbarEl);
|
|
50
|
-
});
|
|
51
67
|
</script>
|
|
52
68
|
|
|
53
69
|
<style scoped>
|
|
@@ -61,7 +77,7 @@ watch(toolbarRef, (toolbarEl) => {
|
|
|
61
77
|
z-index: 999999;
|
|
62
78
|
text-align: left;
|
|
63
79
|
position: fixed;
|
|
64
|
-
--zw-toolbar-offset-y: calc(v-bind("
|
|
80
|
+
--zw-toolbar-offset-y: calc(v-bind("offsets[1]") * 1px)
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
.zw-toolbar::before,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="floatingRef" :style="floatingStyles">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import { computed, ref, toRef } from 'vue';
|
|
9
|
+
import { useFloating, limitShift, offset, shift, autoUpdate } from '@floating-ui/vue';
|
|
10
|
+
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
referenceRef: {
|
|
13
|
+
type: Object,
|
|
14
|
+
required: true
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
placement: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
offsets: {
|
|
23
|
+
type: Array,
|
|
24
|
+
required: true
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const floatingRef = ref(null);
|
|
29
|
+
const referenceRef = toRef(props, 'referenceRef');
|
|
30
|
+
|
|
31
|
+
const middlewares = computed(() => [
|
|
32
|
+
shift({ padding: 16, crossAxis: true, limiter: limitShift() }),
|
|
33
|
+
offset({ crossAxis: props.offsets[0], mainAxis: props.offsets[1] })
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const { floatingStyles } = useFloating(referenceRef, floatingRef, {
|
|
37
|
+
placement: toRef(props, 'placement'),
|
|
38
|
+
strategy: 'fixed',
|
|
39
|
+
middleware: middlewares,
|
|
40
|
+
whileElementsMounted: autoUpdate
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { shallowMount } from '@vue/test-utils';
|
|
2
|
-
import { ref } from 'vue';
|
|
3
2
|
import { Devices } from '../../../enums';
|
|
4
3
|
import Toolbar from '../Toolbar';
|
|
5
4
|
import { ToolbarMobile, ToolbarDesktop, ToolbarPopup } from '../layouts';
|
|
@@ -7,12 +6,11 @@ import { ToolbarMobile, ToolbarDesktop, ToolbarPopup } from '../layouts';
|
|
|
7
6
|
function createComponent({ device, popupMode }) {
|
|
8
7
|
return shallowMount(Toolbar, {
|
|
9
8
|
props: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
offsets: [0, 8]
|
|
14
|
-
},
|
|
9
|
+
visible: true,
|
|
10
|
+
offsets: [0, 8],
|
|
11
|
+
referenceRef: document.createElement('div'),
|
|
15
12
|
device: device ?? Devices.DESKTOP,
|
|
13
|
+
placement: 'bottom',
|
|
16
14
|
popupMode: popupMode ?? false
|
|
17
15
|
}
|
|
18
16
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
class="zw-position--relative"
|
|
5
5
|
icon
|
|
6
6
|
skin="toolbar"
|
|
7
|
-
:active="isOpened"
|
|
7
|
+
:active="toggler.isOpened"
|
|
8
8
|
@click="toggler.open"
|
|
9
9
|
v-tooltip="'Line Height'"
|
|
10
10
|
>
|
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
/>
|
|
19
19
|
</Button>
|
|
20
20
|
|
|
21
|
-
<Modal
|
|
21
|
+
<Modal
|
|
22
|
+
class="zw-line-height-control__modal"
|
|
23
|
+
:toggler="toggler"
|
|
24
|
+
:reference-ref="wrapperRef"
|
|
25
|
+
>
|
|
22
26
|
<FieldLabel class="zw-margin-bottom--xs" field-id="wswg-line-height">
|
|
23
27
|
Line Height
|
|
24
28
|
</FieldLabel>
|
|
@@ -48,22 +52,20 @@
|
|
|
48
52
|
</template>
|
|
49
53
|
|
|
50
54
|
<script setup>
|
|
51
|
-
import {
|
|
55
|
+
import { inject, ref } from 'vue';
|
|
52
56
|
import { Button, Icon, Modal, Range, NumberField, FieldLabel, useModalToggler } from '../../base';
|
|
53
57
|
import { InjectionTokens } from '../../../injectionTokens';
|
|
54
58
|
import { tooltip as vTooltip } from '../../../directives';
|
|
55
59
|
import { TextSettings } from '../../../enums';
|
|
56
60
|
|
|
57
|
-
const wrapperRef = ref(null);
|
|
58
|
-
const modalRef = ref(null);
|
|
59
61
|
const editor = inject(InjectionTokens.EDITOR);
|
|
60
|
-
const
|
|
62
|
+
const wrapperRef = ref(null);
|
|
63
|
+
const toggler = useModalToggler();
|
|
61
64
|
|
|
62
65
|
const currentValue = editor.commands.getLineHeight();
|
|
63
66
|
const isCustomized = editor.commands.isSettingCustomized(TextSettings.LINE_HEIGHT);
|
|
64
67
|
|
|
65
68
|
const apply = (value) => editor.commands.applyLineHeight(String(value));
|
|
66
|
-
const isOpened = computed(() => unref(toggler.isOpened));
|
|
67
69
|
</script>
|
|
68
70
|
|
|
69
71
|
<style scoped>
|
|
@@ -4,8 +4,6 @@ import { InjectionTokens } from '../../../../injectionTokens';
|
|
|
4
4
|
import { Button, Modal, NumberField, Range } from '../../../base';
|
|
5
5
|
import LineHeightControl from '../LineHeightControl';
|
|
6
6
|
|
|
7
|
-
jest.mock('@floating-ui/dom');
|
|
8
|
-
|
|
9
7
|
const SELECTORS = {
|
|
10
8
|
INDICATOR: '[data-test-selector="customizedIndicator"]'
|
|
11
9
|
};
|
|
@@ -112,7 +110,7 @@ describe('rendering', () => {
|
|
|
112
110
|
const modalWrapper = wrapper.findComponent(Modal);
|
|
113
111
|
|
|
114
112
|
expect(buttonWrapper.props('active')).toBe(false);
|
|
115
|
-
expect(modalWrapper.props('toggler').isOpened
|
|
113
|
+
expect(modalWrapper.props('toggler').isOpened).toBe(false);
|
|
116
114
|
});
|
|
117
115
|
|
|
118
116
|
test('should open modal', async () => {
|
|
@@ -124,7 +122,7 @@ describe('rendering', () => {
|
|
|
124
122
|
await nextTick();
|
|
125
123
|
|
|
126
124
|
expect(buttonWrapper.props('active')).toBe(true);
|
|
127
|
-
expect(modalWrapper.props('toggler').isOpened
|
|
125
|
+
expect(modalWrapper.props('toggler').isOpened).toBe(true);
|
|
128
126
|
});
|
|
129
127
|
|
|
130
128
|
test('should render indicator of customized styles', () => {
|