@ckeditor/ckeditor5-link 41.3.1 → 41.4.0-alpha.0

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 (155) hide show
  1. package/build/link.js +1 -1
  2. package/dist/index-content.css +4 -0
  3. package/dist/index-editor.css +44 -0
  4. package/dist/index.css +148 -0
  5. package/dist/index.css.map +1 -0
  6. package/dist/index.js +2422 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/translations/ar.d.ts +8 -0
  9. package/dist/translations/ar.js +5 -0
  10. package/dist/translations/ast.d.ts +8 -0
  11. package/dist/translations/ast.js +5 -0
  12. package/dist/translations/az.d.ts +8 -0
  13. package/dist/translations/az.js +5 -0
  14. package/dist/translations/bg.d.ts +8 -0
  15. package/dist/translations/bg.js +5 -0
  16. package/dist/translations/bn.d.ts +8 -0
  17. package/dist/translations/bn.js +5 -0
  18. package/dist/translations/ca.d.ts +8 -0
  19. package/dist/translations/ca.js +5 -0
  20. package/dist/translations/cs.d.ts +8 -0
  21. package/dist/translations/cs.js +5 -0
  22. package/dist/translations/da.d.ts +8 -0
  23. package/dist/translations/da.js +5 -0
  24. package/dist/translations/de-ch.d.ts +8 -0
  25. package/dist/translations/de-ch.js +5 -0
  26. package/dist/translations/de.d.ts +8 -0
  27. package/dist/translations/de.js +5 -0
  28. package/dist/translations/el.d.ts +8 -0
  29. package/dist/translations/el.js +5 -0
  30. package/dist/translations/en-au.d.ts +8 -0
  31. package/dist/translations/en-au.js +5 -0
  32. package/dist/translations/en-gb.d.ts +8 -0
  33. package/dist/translations/en-gb.js +5 -0
  34. package/dist/translations/en.d.ts +8 -0
  35. package/dist/translations/en.js +5 -0
  36. package/dist/translations/eo.d.ts +8 -0
  37. package/dist/translations/eo.js +5 -0
  38. package/dist/translations/es.d.ts +8 -0
  39. package/dist/translations/es.js +5 -0
  40. package/dist/translations/et.d.ts +8 -0
  41. package/dist/translations/et.js +5 -0
  42. package/dist/translations/eu.d.ts +8 -0
  43. package/dist/translations/eu.js +5 -0
  44. package/dist/translations/fa.d.ts +8 -0
  45. package/dist/translations/fa.js +5 -0
  46. package/dist/translations/fi.d.ts +8 -0
  47. package/dist/translations/fi.js +5 -0
  48. package/dist/translations/fr.d.ts +8 -0
  49. package/dist/translations/fr.js +5 -0
  50. package/dist/translations/gl.d.ts +8 -0
  51. package/dist/translations/gl.js +5 -0
  52. package/dist/translations/he.d.ts +8 -0
  53. package/dist/translations/he.js +5 -0
  54. package/dist/translations/hi.d.ts +8 -0
  55. package/dist/translations/hi.js +5 -0
  56. package/dist/translations/hr.d.ts +8 -0
  57. package/dist/translations/hr.js +5 -0
  58. package/dist/translations/hu.d.ts +8 -0
  59. package/dist/translations/hu.js +5 -0
  60. package/dist/translations/hy.d.ts +8 -0
  61. package/dist/translations/hy.js +5 -0
  62. package/dist/translations/id.d.ts +8 -0
  63. package/dist/translations/id.js +5 -0
  64. package/dist/translations/it.d.ts +8 -0
  65. package/dist/translations/it.js +5 -0
  66. package/dist/translations/ja.d.ts +8 -0
  67. package/dist/translations/ja.js +5 -0
  68. package/dist/translations/km.d.ts +8 -0
  69. package/dist/translations/km.js +5 -0
  70. package/dist/translations/kn.d.ts +8 -0
  71. package/dist/translations/kn.js +5 -0
  72. package/dist/translations/ko.d.ts +8 -0
  73. package/dist/translations/ko.js +5 -0
  74. package/dist/translations/ku.d.ts +8 -0
  75. package/dist/translations/ku.js +5 -0
  76. package/dist/translations/lt.d.ts +8 -0
  77. package/dist/translations/lt.js +5 -0
  78. package/dist/translations/lv.d.ts +8 -0
  79. package/dist/translations/lv.js +5 -0
  80. package/dist/translations/ms.d.ts +8 -0
  81. package/dist/translations/ms.js +5 -0
  82. package/dist/translations/nb.d.ts +8 -0
  83. package/dist/translations/nb.js +5 -0
  84. package/dist/translations/ne.d.ts +8 -0
  85. package/dist/translations/ne.js +5 -0
  86. package/dist/translations/nl.d.ts +8 -0
  87. package/dist/translations/nl.js +5 -0
  88. package/dist/translations/no.d.ts +8 -0
  89. package/dist/translations/no.js +5 -0
  90. package/dist/translations/pl.d.ts +8 -0
  91. package/dist/translations/pl.js +5 -0
  92. package/dist/translations/pt-br.d.ts +8 -0
  93. package/dist/translations/pt-br.js +5 -0
  94. package/dist/translations/pt.d.ts +8 -0
  95. package/dist/translations/pt.js +5 -0
  96. package/dist/translations/ro.d.ts +8 -0
  97. package/dist/translations/ro.js +5 -0
  98. package/dist/translations/ru.d.ts +8 -0
  99. package/dist/translations/ru.js +5 -0
  100. package/dist/translations/sk.d.ts +8 -0
  101. package/dist/translations/sk.js +5 -0
  102. package/dist/translations/sq.d.ts +8 -0
  103. package/dist/translations/sq.js +5 -0
  104. package/dist/translations/sr-latn.d.ts +8 -0
  105. package/dist/translations/sr-latn.js +5 -0
  106. package/dist/translations/sr.d.ts +8 -0
  107. package/dist/translations/sr.js +5 -0
  108. package/dist/translations/sv.d.ts +8 -0
  109. package/dist/translations/sv.js +5 -0
  110. package/dist/translations/th.d.ts +8 -0
  111. package/dist/translations/th.js +5 -0
  112. package/dist/translations/tk.d.ts +8 -0
  113. package/dist/translations/tk.js +5 -0
  114. package/dist/translations/tr.d.ts +8 -0
  115. package/dist/translations/tr.js +5 -0
  116. package/dist/translations/tt.d.ts +8 -0
  117. package/dist/translations/tt.js +5 -0
  118. package/dist/translations/ug.d.ts +8 -0
  119. package/dist/translations/ug.js +5 -0
  120. package/dist/translations/uk.d.ts +8 -0
  121. package/dist/translations/uk.js +5 -0
  122. package/dist/translations/ur.d.ts +8 -0
  123. package/dist/translations/ur.js +5 -0
  124. package/dist/translations/uz.d.ts +8 -0
  125. package/dist/translations/uz.js +5 -0
  126. package/dist/translations/vi.d.ts +8 -0
  127. package/dist/translations/vi.js +5 -0
  128. package/dist/translations/zh-cn.d.ts +8 -0
  129. package/dist/translations/zh-cn.js +5 -0
  130. package/dist/translations/zh.d.ts +8 -0
  131. package/dist/translations/zh.js +5 -0
  132. package/dist/types/augmentation.d.ts +34 -0
  133. package/dist/types/autolink.d.ts +79 -0
  134. package/dist/types/index.d.ts +22 -0
  135. package/dist/types/link.d.ts +31 -0
  136. package/dist/types/linkcommand.d.ts +136 -0
  137. package/dist/types/linkconfig.d.ts +294 -0
  138. package/dist/types/linkediting.d.ts +74 -0
  139. package/dist/types/linkimage.d.ts +31 -0
  140. package/dist/types/linkimageediting.d.ts +43 -0
  141. package/dist/types/linkimageui.d.ts +44 -0
  142. package/dist/types/linkui.d.ts +173 -0
  143. package/dist/types/ui/linkactionsview.d.ts +107 -0
  144. package/dist/types/ui/linkformview.d.ts +175 -0
  145. package/dist/types/unlinkcommand.d.ts +35 -0
  146. package/dist/types/utils/automaticdecorators.d.ts +49 -0
  147. package/dist/types/utils/manualdecorator.d.ts +76 -0
  148. package/dist/types/utils.d.ts +84 -0
  149. package/lang/contexts.json +1 -0
  150. package/package.json +4 -3
  151. package/src/index.d.ts +1 -1
  152. package/src/linkui.js +30 -8
  153. package/src/ui/linkformview.d.ts +31 -1
  154. package/src/ui/linkformview.js +41 -1
  155. package/theme/linkform.css +1 -0
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
7
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
8
+ */
9
+ /**
10
+ * @module link/utils/manualdecorator
11
+ */
12
+ import { type ArrayOrItem } from 'ckeditor5/src/utils.js';
13
+ import type { MatcherObjectPattern } from 'ckeditor5/src/engine.js';
14
+ import type { NormalizedLinkDecoratorManualDefinition } from '../utils.js';
15
+ declare const ManualDecorator_base: {
16
+ new (): import("ckeditor5/src/utils.js").Observable;
17
+ prototype: import("ckeditor5/src/utils.js").Observable;
18
+ };
19
+ /**
20
+ * Helper class that stores manual decorators with observable {@link module:link/utils/manualdecorator~ManualDecorator#value}
21
+ * to support integration with the UI state. An instance of this class is a model with the state of individual manual decorators.
22
+ * These decorators are kept as collections in {@link module:link/linkcommand~LinkCommand#manualDecorators}.
23
+ */
24
+ export default class ManualDecorator extends ManualDecorator_base {
25
+ /**
26
+ * An ID of a manual decorator which is the name of the attribute in the model, for example: 'linkManualDecorator0'.
27
+ */
28
+ id: string;
29
+ /**
30
+ * The value of the current manual decorator. It reflects its state from the UI.
31
+ *
32
+ * @observable
33
+ */
34
+ value: boolean | undefined;
35
+ /**
36
+ * The default value of manual decorator.
37
+ */
38
+ defaultValue?: boolean;
39
+ /**
40
+ * The label used in the user interface to toggle the manual decorator.
41
+ */
42
+ label: string;
43
+ /**
44
+ * A set of attributes added to downcasted data when the decorator is activated for a specific link.
45
+ * Attributes should be added in a form of attributes defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
46
+ */
47
+ attributes?: Record<string, string>;
48
+ /**
49
+ * A set of classes added to downcasted data when the decorator is activated for a specific link.
50
+ * Classes should be added in a form of classes defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
51
+ */
52
+ classes?: ArrayOrItem<string>;
53
+ /**
54
+ * A set of styles added to downcasted data when the decorator is activated for a specific link.
55
+ * Styles should be added in a form of styles defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
56
+ */
57
+ styles?: Record<string, string>;
58
+ /**
59
+ * Creates a new instance of {@link module:link/utils/manualdecorator~ManualDecorator}.
60
+ *
61
+ * @param config.id The name of the attribute used in the model that represents a given manual decorator.
62
+ * For example: `'linkIsExternal'`.
63
+ * @param config.label The label used in the user interface to toggle the manual decorator.
64
+ * @param config.attributes A set of attributes added to output data when the decorator is active for a specific link.
65
+ * Attributes should keep the format of attributes defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
66
+ * @param [config.defaultValue] Controls whether the decorator is "on" by default.
67
+ */
68
+ constructor({ id, label, attributes, classes, styles, defaultValue }: NormalizedLinkDecoratorManualDefinition);
69
+ /**
70
+ * Returns {@link module:engine/view/matcher~MatcherPattern} with decorator attributes.
71
+ *
72
+ * @internal
73
+ */
74
+ _createPattern(): MatcherObjectPattern;
75
+ }
76
+ export {};
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
7
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
8
+ */
9
+ /**
10
+ * @module link/utils
11
+ */
12
+ import type { DowncastConversionApi, Element, Schema, ViewAttributeElement, ViewNode, ViewDocumentFragment } from 'ckeditor5/src/engine.js';
13
+ import type { LocaleTranslate } from 'ckeditor5/src/utils.js';
14
+ import type { LinkDecoratorAutomaticDefinition, LinkDecoratorDefinition, LinkDecoratorManualDefinition } from './linkconfig.js';
15
+ /**
16
+ * A keystroke used by the {@link module:link/linkui~LinkUI link UI feature}.
17
+ */
18
+ export declare const LINK_KEYSTROKE = "Ctrl+K";
19
+ /**
20
+ * Returns `true` if a given view node is the link element.
21
+ */
22
+ export declare function isLinkElement(node: ViewNode | ViewDocumentFragment): boolean;
23
+ /**
24
+ * Creates a link {@link module:engine/view/attributeelement~AttributeElement} with the provided `href` attribute.
25
+ */
26
+ export declare function createLinkElement(href: string, { writer }: DowncastConversionApi): ViewAttributeElement;
27
+ /**
28
+ * Returns a safe URL based on a given value.
29
+ *
30
+ * A URL is considered safe if it is safe for the user (does not contain any malicious code).
31
+ *
32
+ * If a URL is considered unsafe, a simple `"#"` is returned.
33
+ *
34
+ * @internal
35
+ */
36
+ export declare function ensureSafeUrl(url: unknown, allowedProtocols?: Array<string>): string;
37
+ /**
38
+ * Returns the {@link module:link/linkconfig~LinkConfig#decorators `config.link.decorators`} configuration processed
39
+ * to respect the locale of the editor, i.e. to display the {@link module:link/linkconfig~LinkDecoratorManualDefinition label}
40
+ * in the correct language.
41
+ *
42
+ * **Note**: Only the few most commonly used labels are translated automatically. Other labels should be manually
43
+ * translated in the {@link module:link/linkconfig~LinkConfig#decorators `config.link.decorators`} configuration.
44
+ *
45
+ * @param t Shorthand for {@link module:utils/locale~Locale#t Locale#t}.
46
+ * @param decorators The decorator reference where the label values should be localized.
47
+ */
48
+ export declare function getLocalizedDecorators(t: LocaleTranslate, decorators: Array<NormalizedLinkDecoratorDefinition>): Array<NormalizedLinkDecoratorDefinition>;
49
+ /**
50
+ * Converts an object with defined decorators to a normalized array of decorators. The `id` key is added for each decorator and
51
+ * is used as the attribute's name in the model.
52
+ */
53
+ export declare function normalizeDecorators(decorators?: Record<string, LinkDecoratorDefinition>): Array<NormalizedLinkDecoratorDefinition>;
54
+ /**
55
+ * Returns `true` if the specified `element` can be linked (the element allows the `linkHref` attribute).
56
+ */
57
+ export declare function isLinkableElement(element: Element | null, schema: Schema): element is Element;
58
+ /**
59
+ * Returns `true` if the specified `value` is an email.
60
+ */
61
+ export declare function isEmail(value: string): boolean;
62
+ /**
63
+ * Adds the protocol prefix to the specified `link` when:
64
+ *
65
+ * * it does not contain it already, and there is a {@link module:link/linkconfig~LinkConfig#defaultProtocol `defaultProtocol` }
66
+ * configuration value provided,
67
+ * * or the link is an email address.
68
+ */
69
+ export declare function addLinkProtocolIfApplicable(link: string, defaultProtocol?: string): string;
70
+ /**
71
+ * Checks if protocol is already included in the link.
72
+ */
73
+ export declare function linkHasProtocol(link: string): boolean;
74
+ /**
75
+ * Opens the link in a new browser tab.
76
+ */
77
+ export declare function openLink(link: string): void;
78
+ export type NormalizedLinkDecoratorAutomaticDefinition = LinkDecoratorAutomaticDefinition & {
79
+ id: string;
80
+ };
81
+ export type NormalizedLinkDecoratorManualDefinition = LinkDecoratorManualDefinition & {
82
+ id: string;
83
+ };
84
+ export type NormalizedLinkDecoratorDefinition = NormalizedLinkDecoratorAutomaticDefinition | NormalizedLinkDecoratorManualDefinition;
@@ -2,6 +2,7 @@
2
2
  "Unlink": "Toolbar button tooltip for the Unlink feature.",
3
3
  "Link": "Toolbar button tooltip for the Link feature.",
4
4
  "Link URL": "Label for the URL input in the Link URL editing balloon.",
5
+ "Link URL must not be empty.": "An error text displayed when user attempted to enter an empty URL.",
5
6
  "Link image": "Label for the image link button.",
6
7
  "Edit link": "Button opening the Link URL editing balloon.",
7
8
  "Open link in new tab": "Button opening the link in new browser tab.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-link",
3
- "version": "41.3.1",
3
+ "version": "41.4.0-alpha.0",
4
4
  "description": "Link feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -13,8 +13,8 @@
13
13
  "type": "module",
14
14
  "main": "src/index.js",
15
15
  "dependencies": {
16
- "@ckeditor/ckeditor5-ui": "41.3.1",
17
- "ckeditor5": "41.3.1",
16
+ "@ckeditor/ckeditor5-ui": "41.4.0-alpha.0",
17
+ "ckeditor5": "41.4.0-alpha.0",
18
18
  "lodash-es": "4.17.21"
19
19
  },
20
20
  "author": "CKSource (http://cksource.com/)",
@@ -27,6 +27,7 @@
27
27
  "directory": "packages/ckeditor5-link"
28
28
  },
29
29
  "files": [
30
+ "dist",
30
31
  "lang",
31
32
  "src/**/*.js",
32
33
  "src/**/*.d.ts",
package/src/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export { default as LinkImage } from './linkimage.js';
12
12
  export { default as LinkImageEditing } from './linkimageediting.js';
13
13
  export { default as LinkImageUI } from './linkimageui.js';
14
14
  export { default as AutoLink } from './autolink.js';
15
- export { LinkConfig, type LinkDecoratorDefinition } from './linkconfig.js';
15
+ export type { LinkConfig, LinkDecoratorDefinition } from './linkconfig.js';
16
16
  export { default as LinkCommand } from './linkcommand.js';
17
17
  export { default as UnlinkCommand } from './unlinkcommand.js';
18
18
  import './augmentation.js';
package/src/linkui.js CHANGED
@@ -148,19 +148,24 @@ export default class LinkUI extends Plugin {
148
148
  const editor = this.editor;
149
149
  const linkCommand = editor.commands.get('link');
150
150
  const defaultProtocol = editor.config.get('link.defaultProtocol');
151
- const allowCreatingEmptyLinks = editor.config.get('link.allowCreatingEmptyLinks');
152
- const formView = new (CssTransitionDisablerMixin(LinkFormView))(editor.locale, linkCommand);
151
+ const formView = new (CssTransitionDisablerMixin(LinkFormView))(editor.locale, linkCommand, getFormValidators(editor));
153
152
  formView.urlInputView.fieldView.bind('value').to(linkCommand, 'value');
154
153
  // Form elements should be read-only when corresponding commands are disabled.
155
154
  formView.urlInputView.bind('isEnabled').to(linkCommand, 'isEnabled');
156
- // Disable the "save" button if the command is disabled or the input is empty despite being required.
157
- formView.saveButtonView.bind('isEnabled').to(linkCommand, 'isEnabled', formView.urlInputView, 'isEmpty', (isCommandEnabled, isInputEmpty) => isCommandEnabled && (allowCreatingEmptyLinks || !isInputEmpty));
155
+ // Disable the "save" button if the command is disabled.
156
+ formView.saveButtonView.bind('isEnabled').to(linkCommand, 'isEnabled');
158
157
  // Execute link command after clicking the "Save" button.
159
158
  this.listenTo(formView, 'submit', () => {
160
- const { value } = formView.urlInputView.fieldView.element;
161
- const parsedUrl = addLinkProtocolIfApplicable(value, defaultProtocol);
162
- editor.execute('link', parsedUrl, formView.getDecoratorSwitchesState());
163
- this._closeFormView();
159
+ if (formView.isValid()) {
160
+ const { value } = formView.urlInputView.fieldView.element;
161
+ const parsedUrl = addLinkProtocolIfApplicable(value, defaultProtocol);
162
+ editor.execute('link', parsedUrl, formView.getDecoratorSwitchesState());
163
+ this._closeFormView();
164
+ }
165
+ });
166
+ // Update balloon position when form error changes.
167
+ this.listenTo(formView.urlInputView, 'change:errorText', () => {
168
+ editor.ui.update();
164
169
  });
165
170
  // Hide the panel after clicking the "Cancel" button.
166
171
  this.listenTo(formView, 'cancel', () => {
@@ -299,6 +304,7 @@ export default class LinkUI extends Plugin {
299
304
  const editor = this.editor;
300
305
  const linkCommand = editor.commands.get('link');
301
306
  this.formView.disableCssTransitions();
307
+ this.formView.resetFormStatus();
302
308
  this._balloon.add({
303
309
  view: this.formView,
304
310
  position: this._getBalloonPositionData()
@@ -615,3 +621,19 @@ export default class LinkUI extends Plugin {
615
621
  function findLinkElementAncestor(position) {
616
622
  return position.getAncestors().find((ancestor) => isLinkElement(ancestor)) || null;
617
623
  }
624
+ /**
625
+ * Returns link form validation callbacks.
626
+ *
627
+ * @param editor Editor instance.
628
+ */
629
+ function getFormValidators(editor) {
630
+ const t = editor.t;
631
+ const allowCreatingEmptyLinks = editor.config.get('link.allowCreatingEmptyLinks');
632
+ return [
633
+ form => {
634
+ if (!allowCreatingEmptyLinks && !form.url.length) {
635
+ return t('Link URL must not be empty.');
636
+ }
637
+ }
638
+ ];
639
+ }
@@ -46,6 +46,10 @@ export default class LinkFormView extends View {
46
46
  * A collection of child views in the form.
47
47
  */
48
48
  readonly children: ViewCollection;
49
+ /**
50
+ * An array of form validators used by {@link #isValid}.
51
+ */
52
+ private readonly _validators;
49
53
  /**
50
54
  * A collection of views that can be focused in the form.
51
55
  */
@@ -61,8 +65,9 @@ export default class LinkFormView extends View {
61
65
  *
62
66
  * @param locale The localization services instance.
63
67
  * @param linkCommand Reference to {@link module:link/linkcommand~LinkCommand}.
68
+ * @param validators Form validators used by {@link #isValid}.
64
69
  */
65
- constructor(locale: Locale, linkCommand: LinkCommand);
70
+ constructor(locale: Locale, linkCommand: LinkCommand, validators: Array<LinkFormValidatorCallback>);
66
71
  /**
67
72
  * Obtains the state of the {@link module:ui/button/switchbuttonview~SwitchButtonView switch buttons} representing
68
73
  * {@link module:link/linkcommand~LinkCommand#manualDecorators manual link decorators}
@@ -83,6 +88,17 @@ export default class LinkFormView extends View {
83
88
  * Focuses the fist {@link #_focusables} in the form.
84
89
  */
85
90
  focus(): void;
91
+ /**
92
+ * Validates the form and returns `false` when some fields are invalid.
93
+ */
94
+ isValid(): boolean;
95
+ /**
96
+ * Cleans up the supplementary error and information text of the {@link #urlInputView}
97
+ * bringing them back to the state when the form has been displayed for the first time.
98
+ *
99
+ * See {@link #isValid}.
100
+ */
101
+ resetFormStatus(): void;
86
102
  /**
87
103
  * Creates a labeled input view.
88
104
  *
@@ -119,7 +135,21 @@ export default class LinkFormView extends View {
119
135
  * @returns The children of link form view.
120
136
  */
121
137
  private _createFormChildren;
138
+ /**
139
+ * The native DOM `value` of the {@link #urlInputView} element.
140
+ *
141
+ * **Note**: Do not confuse it with the {@link module:ui/inputtext/inputtextview~InputTextView#value}
142
+ * which works one way only and may not represent the actual state of the component in the DOM.
143
+ */
144
+ get url(): string | null;
122
145
  }
146
+ /**
147
+ * Callback used by {@link ~LinkFormView} to check if passed form value is valid.
148
+ *
149
+ * * If `undefined` is returned, it is assumed that the form value is correct and there is no error.
150
+ * * If string is returned, it is assumed that the form value is incorrect and the returned string is displayed in the error label
151
+ */
152
+ export type LinkFormValidatorCallback = (form: LinkFormView) => string | undefined;
123
153
  /**
124
154
  * Fired when the form view is submitted (when one of the children triggered the submit event),
125
155
  * for example with a click on {@link ~LinkFormView#saveButtonView}.
@@ -25,8 +25,9 @@ export default class LinkFormView extends View {
25
25
  *
26
26
  * @param locale The localization services instance.
27
27
  * @param linkCommand Reference to {@link module:link/linkcommand~LinkCommand}.
28
+ * @param validators Form validators used by {@link #isValid}.
28
29
  */
29
- constructor(locale, linkCommand) {
30
+ constructor(locale, linkCommand, validators) {
30
31
  super(locale);
31
32
  /**
32
33
  * Tracks information about DOM focus in the form.
@@ -41,6 +42,7 @@ export default class LinkFormView extends View {
41
42
  */
42
43
  this._focusables = new ViewCollection();
43
44
  const t = locale.t;
45
+ this._validators = validators;
44
46
  this.urlInputView = this._createUrlInput();
45
47
  this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
46
48
  this.saveButtonView.type = 'submit';
@@ -124,6 +126,31 @@ export default class LinkFormView extends View {
124
126
  focus() {
125
127
  this._focusCycler.focusFirst();
126
128
  }
129
+ /**
130
+ * Validates the form and returns `false` when some fields are invalid.
131
+ */
132
+ isValid() {
133
+ this.resetFormStatus();
134
+ for (const validator of this._validators) {
135
+ const errorText = validator(this);
136
+ // One error per field is enough.
137
+ if (errorText) {
138
+ // Apply updated error.
139
+ this.urlInputView.errorText = errorText;
140
+ return false;
141
+ }
142
+ }
143
+ return true;
144
+ }
145
+ /**
146
+ * Cleans up the supplementary error and information text of the {@link #urlInputView}
147
+ * bringing them back to the state when the form has been displayed for the first time.
148
+ *
149
+ * See {@link #isValid}.
150
+ */
151
+ resetFormStatus() {
152
+ this.urlInputView.errorText = null;
153
+ }
127
154
  /**
128
155
  * Creates a labeled input view.
129
156
  *
@@ -229,4 +256,17 @@ export default class LinkFormView extends View {
229
256
  children.add(this.cancelButtonView);
230
257
  return children;
231
258
  }
259
+ /**
260
+ * The native DOM `value` of the {@link #urlInputView} element.
261
+ *
262
+ * **Note**: Do not confuse it with the {@link module:ui/inputtext/inputtextview~InputTextView#value}
263
+ * which works one way only and may not represent the actual state of the component in the DOM.
264
+ */
265
+ get url() {
266
+ const { element } = this.urlInputView.fieldView;
267
+ if (!element) {
268
+ return null;
269
+ }
270
+ return element.value.trim();
271
+ }
232
272
  }
@@ -7,6 +7,7 @@
7
7
 
8
8
  .ck.ck-link-form {
9
9
  display: flex;
10
+ align-items: flex-start;
10
11
 
11
12
  & .ck-label {
12
13
  display: none;