@zipify/wysiwyg 1.0.0-dev.2 → 1.0.0-dev.22

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.
Files changed (150) hide show
  1. package/.eslintignore +1 -0
  2. package/.github/dependabot.yaml +1 -0
  3. package/.release-it.json +3 -1
  4. package/.stylelintignore +1 -0
  5. package/README.md +3 -1
  6. package/config/jest/setupTests.js +7 -1
  7. package/config/webpack/example.config.js +2 -0
  8. package/config/webpack/lib.config.js +44 -0
  9. package/config/webpack/loaders/style-loader.js +3 -1
  10. package/config/webpack/loaders/svg-loader.js +1 -1
  11. package/dist/wysiwyg.css +1226 -0
  12. package/dist/wysiwyg.js +7024 -0
  13. package/example/ExampleApp.vue +51 -32
  14. package/example/example.js +26 -0
  15. package/example/pageBlocks.js +31 -0
  16. package/example/presets.js +4 -2
  17. package/lib/Wysiwyg.vue +41 -21
  18. package/lib/assets/icons/alignment-center.svg +3 -0
  19. package/lib/assets/icons/alignment-justify.svg +3 -0
  20. package/lib/assets/icons/alignment-left.svg +3 -0
  21. package/lib/assets/icons/alignment-right.svg +3 -0
  22. package/lib/assets/icons/arrow.svg +3 -0
  23. package/lib/assets/icons/background-color.svg +3 -0
  24. package/lib/assets/icons/case-style.svg +3 -0
  25. package/lib/assets/icons/font-color.svg +5 -0
  26. package/lib/assets/icons/italic.svg +3 -0
  27. package/lib/assets/icons/line-height.svg +3 -0
  28. package/lib/assets/icons/link.svg +3 -0
  29. package/lib/assets/icons/list-circle.svg +3 -0
  30. package/lib/assets/icons/list-decimal.svg +3 -0
  31. package/lib/assets/icons/list-disc.svg +3 -0
  32. package/lib/assets/icons/list-latin.svg +3 -0
  33. package/lib/assets/icons/list-roman.svg +3 -0
  34. package/lib/assets/icons/list-square.svg +3 -0
  35. package/lib/assets/icons/remove-format.svg +3 -0
  36. package/lib/assets/icons/reset-styles.svg +3 -0
  37. package/lib/assets/icons/strike-through.svg +3 -0
  38. package/lib/assets/icons/superscript.svg +3 -0
  39. package/lib/assets/icons/underline.svg +3 -0
  40. package/lib/assets/icons/unlink.svg +3 -0
  41. package/lib/components/base/Button.vue +21 -1
  42. package/lib/components/base/Checkbox.vue +89 -0
  43. package/lib/components/base/FieldLabel.vue +2 -1
  44. package/lib/components/base/Icon.vue +18 -10
  45. package/lib/components/base/Modal.vue +0 -1
  46. package/lib/components/base/TextField.vue +106 -0
  47. package/lib/components/base/__tests__/Icon.test.js +6 -13
  48. package/lib/components/base/__tests__/TextField.test.js +57 -0
  49. package/lib/components/base/__tests__/__snapshots__/TextField.test.js.snap +9 -0
  50. package/lib/components/base/colorPicker/composables/usePickerHotkeys.js +2 -1
  51. package/lib/components/base/composables/index.js +1 -0
  52. package/lib/components/base/composables/useValidator.js +19 -0
  53. package/lib/components/base/dropdown/Dropdown.vue +15 -3
  54. package/lib/components/base/dropdown/DropdownActivator.vue +19 -3
  55. package/lib/components/base/index.js +3 -1
  56. package/lib/components/toolbar/Toolbar.vue +49 -9
  57. package/lib/components/toolbar/ToolbarFull.vue +10 -2
  58. package/lib/components/toolbar/__tests__/Toolbar.test.js +6 -0
  59. package/lib/components/toolbar/controls/FontSizeControl.vue +7 -0
  60. package/lib/components/toolbar/controls/ListControl.vue +1 -5
  61. package/lib/components/toolbar/controls/UnderlineControl.vue +2 -2
  62. package/lib/components/toolbar/controls/__tests__/UnderlineControl.test.js +4 -0
  63. package/lib/components/toolbar/controls/index.js +1 -0
  64. package/lib/components/toolbar/controls/link/LinkControl.vue +152 -0
  65. package/lib/components/toolbar/controls/link/LinkControlApply.vue +35 -0
  66. package/lib/components/toolbar/controls/link/LinkControlHeader.vue +67 -0
  67. package/lib/components/toolbar/controls/link/composables/index.js +1 -0
  68. package/lib/components/toolbar/controls/link/composables/useLink.js +61 -0
  69. package/lib/components/toolbar/controls/link/destination/LinkControlDestination.vue +103 -0
  70. package/lib/components/toolbar/controls/link/destination/LinkControlPageBlock.vue +54 -0
  71. package/lib/components/toolbar/controls/link/destination/LinkControlUrl.vue +52 -0
  72. package/lib/components/toolbar/controls/link/destination/index.js +1 -0
  73. package/lib/components/toolbar/controls/link/index.js +1 -0
  74. package/lib/composables/__tests__/useEditor.test.js +14 -5
  75. package/lib/composables/useEditor.js +13 -8
  76. package/lib/composables/useToolbar.js +14 -29
  77. package/lib/directives/outClick.js +20 -4
  78. package/lib/enums/LinkDestinations.js +4 -0
  79. package/lib/enums/LinkTargets.js +4 -0
  80. package/lib/enums/TextSettings.js +3 -1
  81. package/lib/enums/index.js +2 -0
  82. package/lib/extensions/Alignment.js +21 -3
  83. package/lib/extensions/BackgroundColor.js +16 -1
  84. package/lib/extensions/FontColor.js +16 -1
  85. package/lib/extensions/FontFamily.js +26 -2
  86. package/lib/extensions/FontSize.js +28 -3
  87. package/lib/extensions/FontStyle.js +23 -2
  88. package/lib/extensions/FontWeight.js +33 -1
  89. package/lib/extensions/LineHeight.js +29 -3
  90. package/lib/extensions/Link.js +101 -0
  91. package/lib/extensions/StylePreset.js +36 -6
  92. package/lib/extensions/TextDecoration.js +29 -3
  93. package/lib/extensions/__tests__/Alignment.test.js +30 -3
  94. package/lib/extensions/__tests__/BackgroundColor.test.js +38 -3
  95. package/lib/extensions/__tests__/CaseStyle.test.js +4 -3
  96. package/lib/extensions/__tests__/FontColor.test.js +38 -3
  97. package/lib/extensions/__tests__/FontFamily.test.js +59 -5
  98. package/lib/extensions/__tests__/FontSize.test.js +38 -3
  99. package/lib/extensions/__tests__/FontStyle.test.js +46 -3
  100. package/lib/extensions/__tests__/FontWeight.test.js +66 -3
  101. package/lib/extensions/__tests__/LineHeight.test.js +49 -3
  102. package/lib/extensions/__tests__/StylePreset.test.js +143 -4
  103. package/lib/extensions/__tests__/TextDecoration.test.js +87 -3
  104. package/lib/extensions/__tests__/__snapshots__/Alignment.test.js.snap +70 -2
  105. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +121 -1
  106. package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +109 -1
  107. package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +179 -1
  108. package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +132 -2
  109. package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +142 -1
  110. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +179 -1
  111. package/lib/extensions/__tests__/__snapshots__/LineHeight.test.js.snap +118 -2
  112. package/lib/extensions/__tests__/__snapshots__/StylePreset.test.js.snap +171 -2
  113. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +300 -3
  114. package/lib/extensions/core/CopyPasteProcessor.js +10 -0
  115. package/lib/extensions/core/TextProcessor.js +10 -0
  116. package/lib/extensions/core/__tests__/NodeProcessor.test.js +4 -3
  117. package/lib/extensions/core/__tests__/SelectionProcessor.test.js +9 -6
  118. package/lib/extensions/core/__tests__/TextProcessor.test.js +139 -10
  119. package/lib/extensions/core/__tests__/__snapshots__/TextProcessor.test.js.snap +26 -0
  120. package/lib/extensions/core/index.js +11 -2
  121. package/lib/extensions/core/plugins/PastePlugin.js +48 -0
  122. package/lib/extensions/core/plugins/ProseMirrorPlugin.js +20 -0
  123. package/lib/extensions/core/plugins/index.js +1 -0
  124. package/lib/extensions/index.js +41 -33
  125. package/lib/extensions/list/List.js +34 -0
  126. package/lib/extensions/list/__tests__/List.test.js +115 -5
  127. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +481 -0
  128. package/lib/injectionTokens.js +2 -1
  129. package/lib/services/ContentNormalizer.js +157 -0
  130. package/lib/services/ContextWidnow.js +23 -0
  131. package/lib/services/Storage.js +1 -13
  132. package/lib/services/__tests__/ContentNormalizer.test.js +74 -0
  133. package/lib/services/__tests__/FavoriteColors.test.js +20 -0
  134. package/lib/services/__tests__/JsonSerializer.test.js +23 -0
  135. package/lib/services/__tests__/Storage.test.js +79 -0
  136. package/lib/services/index.js +2 -0
  137. package/lib/styles/content.css +96 -9
  138. package/lib/styles/helpers/offsets.css +16 -0
  139. package/lib/styles/variables.css +6 -0
  140. package/lib/utils/__tests__/__snapshots__/renderInlineSetting.test.js.snap +4 -4
  141. package/lib/utils/__tests__/convertColor.test.js +19 -0
  142. package/lib/utils/__tests__/createKeyboardShortcut.test.js +25 -0
  143. package/lib/utils/__tests__/renderInlineSetting.test.js +26 -0
  144. package/lib/utils/convertColor.js +7 -0
  145. package/lib/utils/importIcon.js +12 -0
  146. package/lib/utils/index.js +2 -0
  147. package/lib/utils/renderInlineSetting.js +2 -2
  148. package/package.json +18 -14
  149. package/lib/assets/icons.svg +0 -69
  150. package/lib/composables/__tests__/useToolbar.test.js +0 -56
@@ -1,12 +1,15 @@
1
1
  <template>
2
- <svg v-bind="iconAttrs" class="zw-icon" :class="iconClasses">
3
- <use :href="url" />
4
- </svg>
2
+ <div
3
+ class="zw-icon"
4
+ :class="iconClasses"
5
+ :style="iconStyles"
6
+ v-html="source"
7
+ />
5
8
  </template>
6
9
 
7
10
  <script>
8
11
  import { computed } from '@vue/composition-api';
9
- import sprite from '../../assets/icons.svg';
12
+ import { importIcon } from '../../utils';
10
13
 
11
14
  export default {
12
15
  name: 'Icon',
@@ -31,7 +34,7 @@ export default {
31
34
  },
32
35
 
33
36
  setup(props) {
34
- const url = computed(() => `${sprite}#${props.name}`);
37
+ const source = computed(() => importIcon(props.name));
35
38
 
36
39
  const iconSize = computed(() => {
37
40
  if (isNaN(Number(props.size))) return props.size;
@@ -39,8 +42,13 @@ export default {
39
42
  return `${props.size}px`;
40
43
  });
41
44
 
42
- const iconAttrs = computed(() => {
43
- return props.size ? { width: iconSize.value, height: iconSize.value } : null;
45
+ const iconStyles = computed(() => {
46
+ if (!props.size) return null;
47
+
48
+ return {
49
+ '--zw-icon-width': iconSize.value,
50
+ '--zw-icon-height': iconSize.value
51
+ };
44
52
  });
45
53
 
46
54
  const iconClasses = computed(() => ({
@@ -48,8 +56,8 @@ export default {
48
56
  }));
49
57
 
50
58
  return {
51
- url,
52
- iconAttrs,
59
+ source,
60
+ iconStyles,
53
61
  iconClasses
54
62
  };
55
63
  }
@@ -58,7 +66,7 @@ export default {
58
66
 
59
67
  <style scoped>
60
68
  .zw-icon {
61
- display: inline-block;
69
+ display: flex;
62
70
  }
63
71
 
64
72
  .zw-icon--auto-color {
@@ -76,7 +76,6 @@ export default {
76
76
  <style scoped>
77
77
  .zw-modal {
78
78
  border-radius: 2px;
79
- overflow: hidden;
80
79
  box-shadow: 0 0 4px rgba(var(--zw-color-black), 0.3);
81
80
  background-color: rgb(var(--zw-color-n15));
82
81
  max-height: var(--zw-modal-max-height);
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <div class="zw-field">
3
+ <label class="zw-field__label" :for="fieldId" data-test-selector="label">
4
+ {{ label }}
5
+ </label>
6
+
7
+ <input
8
+ class="zw-field__input"
9
+ type="text"
10
+ :value="value"
11
+ :id="fieldId"
12
+ :placeholder="placeholder"
13
+ @input="onInput"
14
+ data-test-selector="input"
15
+ >
16
+
17
+ <p class="zw-field__label--error" v-if="error" data-test-selector="error">
18
+ {{ error }}
19
+ </p>
20
+ </div>
21
+ </template>
22
+
23
+ <script>
24
+ import { computed } from '@vue/composition-api';
25
+
26
+ export default {
27
+ name: 'TextField',
28
+
29
+ props: {
30
+ value: {
31
+ type: [Number, String],
32
+ required: true
33
+ },
34
+
35
+ label: {
36
+ type: String,
37
+ required: true
38
+ },
39
+
40
+ placeholder: {
41
+ type: String,
42
+ required: false,
43
+ default: ''
44
+ },
45
+
46
+ error: {
47
+ type: String,
48
+ required: false,
49
+ default: null
50
+ }
51
+ },
52
+
53
+ setup(props, { emit }) {
54
+ const onInput = (event) => emit('input', event.target.value);
55
+ const fieldId = computed(() => {
56
+ return props.label.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase());
57
+ });
58
+
59
+ return { onInput, fieldId };
60
+ }
61
+ };
62
+ </script>
63
+
64
+ <style lang="scss" scoped>
65
+ .zw-field {
66
+ display: flex;
67
+ flex-direction: column;
68
+ }
69
+
70
+ .zw-field__input {
71
+ --border-color: rgb(var(--zw-color-n60));
72
+ --text-color: rgb(var(--zw-color-n85));
73
+
74
+ border: 1px solid var(--border-color);
75
+ background-color: transparent;
76
+ color: var(--text-color);
77
+ font-size: var(--zw-font-size-xxs);
78
+ outline: none;
79
+ padding: 6px;
80
+ line-height: var(--zw-line-height-xxs);
81
+ }
82
+
83
+ .zw-field__input:hover {
84
+ --border-color: rgb(var(--zw-color-n80));
85
+ --text-color: rgb(var(--zw-color-n85));
86
+ }
87
+
88
+ .zw-field__input:focus,
89
+ .zw-field__input:focus-within {
90
+ --border-color: rgb(var(--zw-color-white));
91
+ --text-color: rgb(var(--zw-color-white));
92
+ }
93
+
94
+ .zw-field__label {
95
+ display: inline-block;
96
+ font-size: var(--zw-font-size-xxs);
97
+ padding-bottom: var(--zw-offset-xxs);
98
+ line-height: var(--zw-line-height-xxs);
99
+ }
100
+
101
+ .zw-field__label--error {
102
+ font-size: var(--zw-font-size-xxs);
103
+ margin: var(--zw-offset-xxs) 0 0;
104
+ color: rgb(var(--zw-color-red));
105
+ }
106
+ </style>
@@ -14,32 +14,25 @@ function createComponent({ name, size, isAutoColor } = {}) {
14
14
  }
15
15
 
16
16
  describe('rendering', () => {
17
- test('should render url to icon', () => {
18
- const wrapper = createComponent({ name: 'close' });
19
- const useWrapper = wrapper.find('use');
20
-
21
- expect(useWrapper.attributes('href')).toBe('http://zipify.com/icons.svg#close');
22
- });
23
-
24
17
  test('should render without size', () => {
25
18
  const wrapper = createComponent({ size: '' });
26
19
 
27
- expect(wrapper.attributes('width')).toBeFalsy();
28
- expect(wrapper.attributes('height')).toBeFalsy();
20
+ expect(wrapper.element).not.toElementHasStyle('--zw-icon-width');
21
+ expect(wrapper.element).not.toElementHasStyle('--zw-icon-height');
29
22
  });
30
23
 
31
24
  test('should render size without units', () => {
32
25
  const wrapper = createComponent({ size: '12' });
33
26
 
34
- expect(wrapper.attributes('width')).toBe('12px');
35
- expect(wrapper.attributes('height')).toBe('12px');
27
+ expect(wrapper.element).toElementHasStyle('--zw-icon-width', '12px');
28
+ expect(wrapper.element).toElementHasStyle('--zw-icon-height', '12px');
36
29
  });
37
30
 
38
31
  test('should percent size', () => {
39
32
  const wrapper = createComponent({ size: '12%' });
40
33
 
41
- expect(wrapper.attributes('width')).toBe('12%');
42
- expect(wrapper.attributes('height')).toBe('12%');
34
+ expect(wrapper.element).toElementHasStyle('--zw-icon-width', '12%');
35
+ expect(wrapper.element).toElementHasStyle('--zw-icon-height', '12%');
43
36
  });
44
37
 
45
38
  test('should render auto color style', () => {
@@ -0,0 +1,57 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import TextField from '../TextField';
3
+
4
+ function createComponent(label, error) {
5
+ return shallowMount(TextField, {
6
+ propsData: {
7
+ value: '',
8
+ label: label ?? '',
9
+ placeholder: '',
10
+ error: error ?? null
11
+ }
12
+ });
13
+ }
14
+
15
+ const SELECTORS = {
16
+ LABEL: '[data-test-selector="label"]',
17
+ INPUT: '[data-test-selector="input"]',
18
+ ERROR: '[data-test-selector="error"]'
19
+ };
20
+
21
+ describe('rendering', () => {
22
+ test('should render label', () => {
23
+ const label = 'Hello world!';
24
+ const wrapper = createComponent(label);
25
+
26
+ expect(wrapper.find(SELECTORS.LABEL).text()).toEqual(label);
27
+ });
28
+
29
+ test('should generate label', () => {
30
+ const wrapper = createComponent('hello world label');
31
+
32
+ expect(wrapper.find(SELECTORS.INPUT).element.id).toBe('helloWorldLabel');
33
+ });
34
+
35
+ test('should render errors', () => {
36
+ const wrapper = createComponent('_', 'some error');
37
+
38
+ expect(wrapper.contains(SELECTORS.ERROR)).toBeTruthy();
39
+ });
40
+
41
+ test('should not render errors', () => {
42
+ const wrapper = createComponent();
43
+
44
+ expect(wrapper.contains(SELECTORS.ERROR)).toBeFalsy();
45
+ });
46
+ });
47
+
48
+ describe('input change handling', () => {
49
+ test('should emit parsed value', () => {
50
+ const wrapper = createComponent();
51
+ const inputComponentWrapper = wrapper.find(SELECTORS.INPUT);
52
+
53
+ inputComponentWrapper.setValue('Hello world!!!');
54
+
55
+ expect(wrapper.emitted('input')).toMatchSnapshot();
56
+ });
57
+ });
@@ -0,0 +1,9 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`input change handling should emit parsed value 1`] = `
4
+ Array [
5
+ Array [
6
+ "Hello world!!!",
7
+ ],
8
+ ]
9
+ `;
@@ -1,4 +1,5 @@
1
1
  import { ref } from '@vue/composition-api';
2
+ import { ContextWindow } from '../../../../services';
2
3
  import { useActivatedListener } from '../../composables';
3
4
 
4
5
  export function usePickerHotkeys({ isOpenedRef, onCancel, onApply }) {
@@ -16,7 +17,7 @@ export function usePickerHotkeys({ isOpenedRef, onCancel, onApply }) {
16
17
  }
17
18
 
18
19
  useActivatedListener({
19
- targetRef: ref(document),
20
+ targetRef: ref(ContextWindow.document),
20
21
  isActiveRef: isOpenedRef,
21
22
  event: 'keydown',
22
23
  onEvent: handle,
@@ -1,3 +1,4 @@
1
+ export { useValidator } from './useValidator';
1
2
  export { useTempValue } from './useTempValue';
2
3
  export { useElementRef } from './useElementRef';
3
4
  export { useNumberValue } from './useNumberValue';
@@ -0,0 +1,19 @@
1
+ import { ref } from '@vue/composition-api';
2
+
3
+ export function useValidator({ validations }) {
4
+ const error = ref(null);
5
+
6
+ function validate(value) {
7
+ for (const validate of validations) {
8
+ const validationError = validate(value);
9
+
10
+ if (validationError) {
11
+ return error.value = validationError;
12
+ }
13
+
14
+ error.value = null;
15
+ }
16
+ }
17
+
18
+ return { error, validate };
19
+ }
@@ -1,12 +1,12 @@
1
1
  <template>
2
2
  <div class="zw-dropdown" ref="dropdownRef">
3
- <DropdownActivator>
3
+ <DropdownActivator :color="color">
4
4
  <template #default="attrs">
5
5
  <slot name="activator" v-bind="attrs" />
6
6
  </template>
7
7
  </DropdownActivator>
8
8
 
9
- <Modal max-height="300" max-width="156" :toggler="toggler" ref="modalRef">
9
+ <Modal max-height="300" :max-width="maxWidth" :toggler="toggler" ref="modalRef">
10
10
  <DropdownMenu :options="options">
11
11
  <template #option="attrs">
12
12
  <slot name="option" v-bind="attrs" />
@@ -40,7 +40,7 @@ export default {
40
40
 
41
41
  props: {
42
42
  value: {
43
- type: String,
43
+ type: [String, Number],
44
44
  required: false,
45
45
  default: null
46
46
  },
@@ -48,6 +48,18 @@ export default {
48
48
  options: {
49
49
  type: Array,
50
50
  required: true
51
+ },
52
+
53
+ maxWidth: {
54
+ type: Number,
55
+ required: false,
56
+ default: 156
57
+ },
58
+
59
+ color: {
60
+ type: String,
61
+ required: false,
62
+ default: 'none'
51
63
  }
52
64
  },
53
65
 
@@ -24,7 +24,7 @@
24
24
  </template>
25
25
 
26
26
  <script>
27
- import { computed, inject } from '@vue/composition-api';
27
+ import { computed, inject, toRef } from '@vue/composition-api';
28
28
  import Button from '../Button';
29
29
  import Icon from '../Icon';
30
30
  import { InjectionTokens } from './injectionTokens';
@@ -42,14 +42,24 @@ export default {
42
42
  event: 'change'
43
43
  },
44
44
 
45
- setup() {
45
+ props: {
46
+ color: {
47
+ type: String,
48
+ required: false,
49
+ default: 'none'
50
+ }
51
+ },
52
+
53
+ setup(props) {
46
54
  const activeOptionManager = inject(InjectionTokens.ACTIVE_MANAGER);
47
55
  const dropdownToggler = inject(InjectionTokens.TOGGLER);
56
+ const color = toRef(props, 'color');
48
57
 
49
58
  const activeOptionTitle = useDropdownEntityTitle(activeOptionManager.activeOption);
50
59
 
51
60
  const dropdownClasses = computed(() => ({
52
- 'zw-dropdown__activator--active': dropdownToggler.isOpened.value
61
+ 'zw-dropdown__activator--active': dropdownToggler.isOpened.value,
62
+ 'zw-dropdown__activator--gray': color.value === 'gray'
53
63
  }));
54
64
 
55
65
  return {
@@ -78,4 +88,10 @@ export default {
78
88
  .zw-dropdown__activator--active .zw-dropdown__activator-arrow {
79
89
  transform: rotateX(180deg);
80
90
  }
91
+
92
+ .zw-dropdown__activator--gray {
93
+ background-color: rgb(var(--zw-color-n20));
94
+ font-size: var(--zw-font-size-xxs);
95
+ color: rgb(var(--zw-color-white));
96
+ }
81
97
  </style>
@@ -6,6 +6,8 @@ export { default as FieldLabel } from './FieldLabel';
6
6
  export { default as Range } from './Range';
7
7
  export { default as NumberField } from './NumberField';
8
8
  export { default as Modal } from './Modal';
9
- export { useModalToggler } from './composables';
9
+ export { default as TextField } from './TextField';
10
+ export { default as Checkbox } from './Checkbox';
11
+ export { useModalToggler, useElementRef } from './composables';
10
12
  export * from './dropdown';
11
13
  export * from './colorPicker';
@@ -1,13 +1,15 @@
1
1
  <template>
2
- <Transition name="zw-toolbar-" :duration="800">
3
- <div class="zw-toolbar">
4
- <component :is="toolbarComponent" />
5
- </div>
6
- </Transition>
2
+ <keep-alive>
3
+ <transition name="zw-toolbar-" duration="150">
4
+ <div class="zw-toolbar" :style="toolbarStyles" ref="toolbarRef" v-if="isVisible">
5
+ <component :is="toolbarComponent" />
6
+ </div>
7
+ </transition>
8
+ </keep-alive>
7
9
  </template>
8
10
 
9
11
  <script>
10
- import { computed } from '@vue/composition-api';
12
+ import { computed, ref, watch } from '@vue/composition-api';
11
13
  import { Devices } from '../../enums';
12
14
  import ToolbarFull from './ToolbarFull';
13
15
  import ToolbarDevice from './ToolbarDevice';
@@ -19,6 +21,11 @@ export default {
19
21
  device: {
20
22
  type: String,
21
23
  required: true
24
+ },
25
+
26
+ toolbar: {
27
+ type: Object,
28
+ required: true
22
29
  }
23
30
  },
24
31
 
@@ -26,8 +33,23 @@ export default {
26
33
  const toolbarComponent = computed(() => {
27
34
  return props.device === Devices.DESKTOP ? ToolbarFull : ToolbarDevice;
28
35
  });
36
+ const isVisible = computed(() => props.toolbar.isActiveRef.value);
37
+ const toolbarRef = ref(null);
38
+
39
+ watch(toolbarRef, (toolbarEl) => {
40
+ toolbarEl && props.toolbar.mount(toolbarEl);
41
+ });
42
+
43
+ const toolbarStyles = computed(() => ({
44
+ '--zw-toolbar-offset-y': `${props.toolbar.offsets[1]}px`
45
+ }));
29
46
 
30
- return { toolbarComponent };
47
+ return {
48
+ toolbarComponent,
49
+ isVisible,
50
+ toolbarRef,
51
+ toolbarStyles
52
+ };
31
53
  }
32
54
  };
33
55
  </script>
@@ -37,12 +59,30 @@ export default {
37
59
  border-radius: 2px;
38
60
  background-color: rgb(var(--zw-color-n15));
39
61
  color: rgb(var(--zw-color-n70));
40
- z-index: 999;
62
+ z-index: 999999;
63
+ }
64
+
65
+ .zw-toolbar::before,
66
+ .zw-toolbar::after {
67
+ content: "";
68
+ display: block;
69
+ width: 100%;
70
+ height: calc(var(--zw-toolbar-offset-y) + 4px);
71
+ position: absolute;
72
+ --zw-toolbar-safe-zone: calc(-1 * var(--zw-toolbar-offset-y));
73
+ }
74
+
75
+ .zw-toolbar::before {
76
+ top: var(--zw-toolbar-safe-zone);
77
+ }
78
+
79
+ .zw-toolbar::after {
80
+ bottom: var(--zw-toolbar-safe-zone);
41
81
  }
42
82
 
43
83
  .zw-toolbar--enter-active,
44
84
  .zw-toolbar--leave-active {
45
- transition: opacity 0.15s ease-out;
85
+ transition: opacity 150ms ease-out;
46
86
  }
47
87
 
48
88
  .zw-toolbar--leave-active {
@@ -33,12 +33,18 @@
33
33
 
34
34
  <ToolbarGroup>
35
35
  <LineHeightControl />
36
+ </ToolbarGroup>
37
+
38
+ <ToolbarDivider vertical />
39
+
40
+ <ToolbarGroup>
36
41
  <ListControl />
37
42
  </ToolbarGroup>
38
43
 
39
44
  <ToolbarDivider vertical />
40
45
 
41
46
  <ToolbarGroup>
47
+ <LinkControl />
42
48
  <RemoveFormatControl />
43
49
  </ToolbarGroup>
44
50
  </ToolbarRow>
@@ -64,7 +70,8 @@ import {
64
70
  SuperscriptControl,
65
71
  UnderlineControl,
66
72
  ListControl,
67
- RemoveFormatControl
73
+ RemoveFormatControl,
74
+ LinkControl
68
75
  } from './controls';
69
76
 
70
77
  export default {
@@ -88,7 +95,8 @@ export default {
88
95
  AlignmentControl,
89
96
  LineHeightControl,
90
97
  ListControl,
91
- RemoveFormatControl
98
+ RemoveFormatControl,
99
+ LinkControl
92
100
  }
93
101
  };
94
102
  </script>
@@ -1,4 +1,5 @@
1
1
  import { shallowMount } from '@vue/test-utils';
2
+ import { ref } from '@vue/composition-api';
2
3
  import { Devices } from '../../../enums';
3
4
  import Toolbar from '../Toolbar';
4
5
  import ToolbarFull from '../ToolbarFull';
@@ -7,6 +8,11 @@ import ToolbarDevice from '../ToolbarDevice';
7
8
  function createComponent({ device }) {
8
9
  return shallowMount(Toolbar, {
9
10
  propsData: {
11
+ toolbar: {
12
+ mount: jest.fn(),
13
+ isActiveRef: ref(true),
14
+ offsets: [0, 8]
15
+ },
10
16
  device: device ?? Devices.DESKTOP
11
17
  }
12
18
  });
@@ -1,5 +1,6 @@
1
1
  <template>
2
2
  <Dropdown
3
+ class="zw-font-size-control"
3
4
  :options="options"
4
5
  :value="currentValue"
5
6
  @change="apply"
@@ -43,3 +44,9 @@ export default {
43
44
  }
44
45
  };
45
46
  </script>
47
+
48
+ <style scoped>
49
+ .zw-font-size-control {
50
+ width: 64px;
51
+ }
52
+ </style>
@@ -17,11 +17,7 @@
17
17
  </template>
18
18
 
19
19
  <template #option="{ option }">
20
- <DropdownOption
21
- class="zw-list-control__option"
22
- :option="option"
23
- v-tooltip="option.tooltip"
24
- >
20
+ <DropdownOption class="zw-list-control__option" :option="option">
25
21
  <Icon :name="option.icon" size="28px" auto-color />
26
22
  </DropdownOption>
27
23
  </template>
@@ -11,7 +11,7 @@
11
11
  </template>
12
12
 
13
13
  <script>
14
- import { inject } from '@vue/composition-api';
14
+ import { inject, computed } from '@vue/composition-api';
15
15
  import { Button, Icon } from '../../base';
16
16
  import { InjectionTokens } from '../../../injectionTokens';
17
17
  import { tooltip } from '../../../directives';
@@ -31,7 +31,7 @@ export default {
31
31
  setup() {
32
32
  const editor = inject(InjectionTokens.EDITOR);
33
33
 
34
- const currentValue = editor.commands.isUnderline();
34
+ const currentValue = computed(() => editor.commands.isUnderline().value);
35
35
  const apply = () => editor.chain().focus().toggleUnderline().run();
36
36
 
37
37
  return {
@@ -12,6 +12,10 @@ const createEditor = ({ isUnderline } = {}) => ({
12
12
  run: jest.fn()
13
13
  },
14
14
 
15
+ isActive() {
16
+ return false;
17
+ },
18
+
15
19
  chain() {
16
20
  return this.commands;
17
21
  }
@@ -14,3 +14,4 @@ export { default as AlignmentDeviceControl } from './AlignmentDeviceControl';
14
14
  export { default as LineHeightControl } from './LineHeightControl';
15
15
  export { default as ListControl } from './ListControl';
16
16
  export { default as RemoveFormatControl } from './RemoveFormatControl';
17
+ export { LinkControl } from './link';