ckeditor5-elements-schema 0.0.0-stage → 49.0.0-alpha.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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -2
  3. package/dist/augmentation.d.ts +19 -0
  4. package/dist/augmentation.d.ts.map +1 -0
  5. package/dist/augmentation.js +2 -0
  6. package/dist/augmentation.js.map +1 -0
  7. package/dist/canHold.d.ts +10 -0
  8. package/dist/canHold.d.ts.map +1 -0
  9. package/dist/canHold.js +22 -0
  10. package/dist/canHold.js.map +1 -0
  11. package/dist/compileElementSchema.d.ts +41 -0
  12. package/dist/compileElementSchema.d.ts.map +1 -0
  13. package/dist/compileElementSchema.js +60 -0
  14. package/dist/compileElementSchema.js.map +1 -0
  15. package/dist/convertElementSchema.d.ts +16 -0
  16. package/dist/convertElementSchema.d.ts.map +1 -0
  17. package/dist/convertElementSchema.js +43 -0
  18. package/dist/convertElementSchema.js.map +1 -0
  19. package/dist/elementSchema.d.ts +47 -0
  20. package/dist/elementSchema.d.ts.map +1 -0
  21. package/dist/elementSchema.js +108 -0
  22. package/dist/elementSchema.js.map +1 -0
  23. package/dist/elementSchemaError.d.ts +12 -0
  24. package/dist/elementSchemaError.d.ts.map +1 -0
  25. package/dist/elementSchemaError.js +16 -0
  26. package/dist/elementSchemaError.js.map +1 -0
  27. package/dist/index.d.ts +10 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +12 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/readDefinition.d.ts +18 -0
  32. package/dist/readDefinition.d.ts.map +1 -0
  33. package/dist/readDefinition.js +60 -0
  34. package/dist/readDefinition.js.map +1 -0
  35. package/dist/suggest.d.ts +5 -0
  36. package/dist/suggest.d.ts.map +1 -0
  37. package/dist/suggest.js +27 -0
  38. package/dist/suggest.js.map +1 -0
  39. package/dist/types.d.ts +105 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/validate.d.ts +12 -0
  44. package/dist/validate.d.ts.map +1 -0
  45. package/dist/validate.js +11 -0
  46. package/dist/validate.js.map +1 -0
  47. package/dist/validateElementSchema.d.ts +26 -0
  48. package/dist/validateElementSchema.d.ts.map +1 -0
  49. package/dist/validateElementSchema.js +145 -0
  50. package/dist/validateElementSchema.js.map +1 -0
  51. package/package.json +49 -3
  52. package/src/augmentation.ts +21 -0
  53. package/src/canHold.ts +29 -0
  54. package/src/compileElementSchema.ts +88 -0
  55. package/src/convertElementSchema.ts +51 -0
  56. package/src/elementSchema.ts +133 -0
  57. package/src/elementSchemaError.ts +21 -0
  58. package/src/index.ts +14 -0
  59. package/src/readDefinition.ts +98 -0
  60. package/src/suggest.ts +38 -0
  61. package/src/types.ts +123 -0
  62. package/src/validate.ts +20 -0
  63. package/src/validateElementSchema.ts +238 -0
@@ -0,0 +1,51 @@
1
+ import type { Editor } from 'ckeditor5';
2
+ import type { ElementSchemaElement, ElementSchemaInput } from './types.js';
3
+
4
+ /**
5
+ * Returns the tag of an element in the data: a `span` for what is in a line of text (an inline element that is empty or
6
+ * has text), a `div` for the rest. An inline container is a `div`: it holds blocks, which a `span` cannot hold.
7
+ */
8
+ export function getViewTag({ type, isInline }: Pick<ElementSchemaElement, 'type' | 'isInline'>): 'div' | 'span' {
9
+ return isInline && type !== 'container' ? 'span' : 'div';
10
+ }
11
+
12
+ /**
13
+ * Declares the converters of the elements of a schema, one to one: an element is a `div` or a `span` in the data (see
14
+ * {@link getViewTag}), with its name as the class. Its attributes are `data-*` attributes. The data is loaded as it is
15
+ * saved. The editing view is the same, except for the tag, when the element has an `editingTag`.
16
+ *
17
+ * The `empty` elements are empty in the data too: `<div class="divider"></div>`, with no filler.
18
+ */
19
+ export function convertElementSchema(editor: Editor, input: ElementSchemaInput): void {
20
+ const { conversion } = editor;
21
+
22
+ for (const [name, element] of Object.entries(input)) {
23
+ const { type, editingTag, allowAttributes } = element as ElementSchemaElement;
24
+ const tag = getViewTag(element as ElementSchemaElement);
25
+ const view = { name: tag, classes: name };
26
+
27
+ for (const key of allowAttributes ?? []) {
28
+ // The names of HTML attributes are lowercase, so the data has them in lowercase; the model keeps the key as it is.
29
+ conversion.attributeToAttribute({ model: { name, key }, view: `data-${key.toLowerCase()}` });
30
+ }
31
+
32
+ // Upcast, data downcast and editing downcast at once; what differs is declared with a higher priority.
33
+ conversion.elementToElement({ model: name, view });
34
+
35
+ if (type === 'empty') {
36
+ conversion.for('dataDowncast').elementToElement({
37
+ model: name,
38
+ view: (_element, { writer }) => writer.createEmptyElement(tag, { class: name }),
39
+ converterPriority: 'high'
40
+ });
41
+ }
42
+
43
+ if (editingTag) {
44
+ conversion.for('editingDowncast').elementToElement({
45
+ model: name,
46
+ view: { name: editingTag, classes: name },
47
+ converterPriority: 'high'
48
+ });
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,133 @@
1
+ import { Plugin, type ModelSchema } from 'ckeditor5';
2
+ import { findPlugin } from 'ckeditor5-elements-core';
3
+ import { compileElementSchema } from './compileElementSchema.js';
4
+ import { convertElementSchema } from './convertElementSchema.js';
5
+ import ElementSchemaError from './elementSchemaError.js';
6
+ import type { ElementSchemaInput, ElementSchemaKind } from './types.js';
7
+ import { validateElementSchema } from './validateElementSchema.js';
8
+
9
+ /**
10
+ * Declares elements in the model schema of the editor from a simple definition, the `elementSchema` editor option: a
11
+ * plain object, e.g. a JSON file.
12
+ *
13
+ * ```json
14
+ * {
15
+ * "note": { "type": "container", "allowAttributes": ["type"] },
16
+ * "title": { "type": "block", "allowIn": ["note"] },
17
+ * "badge": { "type": "empty", "isInline": true, "allowAttributes": ["color"] }
18
+ * }
19
+ * ```
20
+ *
21
+ * Every element has one of three types, instead of CKEditor's many `is*` flags and generic items:
22
+ *
23
+ * * `empty`: has no content, like an image. It is an object, selected as a whole. With `isInline`, it is in a line of text.
24
+ * * `block`: can contain text and inline elements, like a paragraph. With `isInline`, it is an inline element with text,
25
+ * like a bold that is an element with attributes.
26
+ * * `container`: cannot contain text, but blocks, containers and empty elements, like a block quote.
27
+ *
28
+ * `allowAttributes` and `allowChildren` work as in CKEditor's schema. `allowIn` too, but it replaces the default places.
29
+ * See {@link ElementSchemaElement}.
30
+ *
31
+ * The schema is validated first, and a schema that is contradictory or refers to unknown elements is not accepted: the
32
+ * editor is not created, and the {@link ElementSchemaError} lists all the problems (see {@link validateElementSchema}).
33
+ *
34
+ * The elements are registered after the plugins are initialized (in `afterInit()`), so they can refer to the elements
35
+ * of other plugins, e.g. `paragraph`. Plugins that extend or convert the elements must do it in `afterInit()` too, or
36
+ * later; the converters can be declared at any time.
37
+ *
38
+ * The `empty` and `container` elements can be selected as a whole, by a click on them and by the arrow keys, when
39
+ * the `ElementSelectable` plugin is loaded too: the element schema does not depend on it, and uses it when it is there
40
+ * (with `elementSchemaConverters`, it warns when it is not).
41
+ *
42
+ * The plugin declares the schema only: how the elements look in the editor and in the data is declared with
43
+ * converters, or by the plugin when the `elementSchemaConverters` option is `true`: one to one, as `div` and `span`.
44
+ */
45
+ export default class ElementSchema extends Plugin {
46
+ public static get pluginName(): 'ElementSchema' {
47
+ return 'ElementSchema' as const;
48
+ }
49
+
50
+ public afterInit(): void {
51
+ const input = this.editor.config.get('elementSchema');
52
+
53
+ if (input === undefined) {
54
+ return;
55
+ }
56
+
57
+ const schema = this.editor.model.schema;
58
+ const issues = validateElementSchema(input, { external: getExternalKinds(schema) });
59
+
60
+ if (issues.length) {
61
+ throw new ElementSchemaError(issues);
62
+ }
63
+
64
+ const { items, children } = compileElementSchema(input);
65
+
66
+ for (const [name, definition] of items) {
67
+ schema.register(name, definition);
68
+ }
69
+
70
+ for (const { parent, child } of children) {
71
+ schema.extend(child, { allowIn: parent });
72
+ }
73
+
74
+ this._makeSelectable(input);
75
+
76
+ if (this.editor.config.get('elementSchemaConverters')) {
77
+ convertElementSchema(this.editor, input);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Makes the `empty` elements and the containers selectable as a whole, by a click on them and by the arrow keys, with
83
+ * `ElementSelectable`. Not the blocks: a click in a block puts the cursor in its text.
84
+ */
85
+ private _makeSelectable(input: ElementSchemaInput): void {
86
+ const names = Object.entries(input)
87
+ .filter(([, element]) => element.type === 'empty' || element.type === 'container')
88
+ .map(([name]) => name);
89
+ const selectable = findPlugin(this.editor, 'ElementSelectable', isSelectablePlugin);
90
+
91
+ if (selectable) {
92
+ selectable.add(...names);
93
+ } else if (names.length && this.editor.config.get('elementSchemaConverters')) {
94
+ // The converters make the elements, and nothing selects them: say it, as it is a silent loss. Not CKEditor's
95
+ // `logWarning()`: it links to CKEditor's list of errors, which does not have this one.
96
+ // eslint-disable-next-line no-console -- A warning for the developer, once, when the editor is created.
97
+ console.warn(
98
+ `ElementSchema: load ElementSelectable to select the elements of the schema as a whole: ${names.join(', ')}.`
99
+ );
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * The part of the `ElementSelectable` plugin the element schema uses. The schema does not depend on the other features:
106
+ * the plugin is looked up by name and checked against this interface.
107
+ */
108
+ interface SelectablePlugin {
109
+ add(...names: string[]): void;
110
+ }
111
+
112
+ function isSelectablePlugin(plugin: object): plugin is SelectablePlugin {
113
+ return 'add' in plugin && typeof plugin.add === 'function';
114
+ }
115
+
116
+ /**
117
+ * Returns the kinds of the elements of other plugins: inline or not, and `empty` for objects, `block` for what can
118
+ * contain text, `container` for the rest. The generic items (`$block`, …) are not elements.
119
+ */
120
+ function getExternalKinds(schema: ModelSchema): Record<string, ElementSchemaKind> {
121
+ const kinds: Record<string, ElementSchemaKind> = {};
122
+
123
+ for (const name of Object.keys(schema.getDefinitions())) {
124
+ if (!name.startsWith('$')) {
125
+ kinds[name] = {
126
+ isInline: schema.isInline(name),
127
+ type: schema.isObject(name) ? 'empty' : schema.checkChild(name, '$text') ? 'block' : 'container'
128
+ };
129
+ }
130
+ }
131
+
132
+ return kinds;
133
+ }
@@ -0,0 +1,21 @@
1
+ import type { ElementSchemaIssue } from './types.js';
2
+
3
+ /**
4
+ * Thrown when the element schema is not valid. It lists all the problems, and has them in {@link issues}.
5
+ */
6
+ export default class ElementSchemaError extends Error {
7
+ /**
8
+ * The problems of the schema.
9
+ */
10
+ public readonly issues: readonly ElementSchemaIssue[];
11
+
12
+ constructor(issues: readonly ElementSchemaIssue[]) {
13
+ super(
14
+ `The element schema is not valid (${String(issues.length)} ${issues.length === 1 ? 'problem' : 'problems'}):\n` +
15
+ issues.map(({ path, message }) => `- ${path ? `${path}: ` : ''}${message}`).join('\n')
16
+ );
17
+
18
+ this.name = 'ElementSchemaError';
19
+ this.issues = issues;
20
+ }
21
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Element schema: declare elements in the model schema from a simple definition, e.g. a JSON file, with three types of
3
+ * elements (`empty`, `block`, `container`), and a validation that rejects contradictory definitions.
4
+ *
5
+ * @module ckeditor5-elements-schema
6
+ */
7
+
8
+ // Plugins.
9
+ export { default as ElementSchema } from './elementSchema.js';
10
+
11
+ // Validation and compiling, which do not need an editor (also in `validate.ts`, for Node.js).
12
+ export * from './validate.js';
13
+
14
+ import './augmentation.js';
@@ -0,0 +1,98 @@
1
+ import type { ElementSchemaKind } from './types.js';
2
+ import { suggest } from './suggest.js';
3
+
4
+ /**
5
+ * Reports a problem of the schema, at the path.
6
+ */
7
+ export type Report = (path: string, message: string) => void;
8
+
9
+ const TYPES = ['empty', 'block', 'container'];
10
+ export const ELEMENT_KEYS = ['type', 'isInline', 'allowChildren', 'allowIn', 'allowAttributes', 'editingTag'];
11
+
12
+ export const ELEMENT_NAME = /^[a-z][A-Za-z0-9]*$/;
13
+ export const ATTRIBUTE_NAME = /^[A-Za-z][\w.:-]*$/;
14
+ export const TAG_NAME = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
15
+
16
+ export function readKind(definition: Record<string, unknown>, path: string, report: Report): ElementSchemaKind | null {
17
+ const { type, isInline } = definition;
18
+
19
+ if (isInline !== undefined && typeof isInline !== 'boolean') {
20
+ report(`${path}.isInline`, 'must be true or false.');
21
+ }
22
+
23
+ if (type === undefined) {
24
+ report(`${path}.type`, 'is required: "empty", "block" or "container".');
25
+
26
+ return null;
27
+ }
28
+
29
+ if (typeof type !== 'string' || !TYPES.includes(type)) {
30
+ report(
31
+ `${path}.type`,
32
+ `must be "empty", "block" or "container", not ${JSON.stringify(type)}${typeof type === 'string' ? suggest(type, TYPES) : ''}.`
33
+ );
34
+
35
+ return null;
36
+ }
37
+
38
+ return { type: type as ElementSchemaKind['type'], isInline: isInline === true };
39
+ }
40
+
41
+ /**
42
+ * Reads a list of names, and reports what is not a list of unique, non-empty strings. Returns the list, or
43
+ * `undefined` when it is missing.
44
+ */
45
+ export function readNames(
46
+ definition: Record<string, unknown>,
47
+ key: string,
48
+ path: string,
49
+ report: Report
50
+ ): string[] | undefined {
51
+ const value = definition[key];
52
+
53
+ if (value === undefined) {
54
+ return undefined;
55
+ }
56
+
57
+ if (!Array.isArray(value)) {
58
+ report(`${path}.${key}`, `must be an array of names, e.g. ["a", "b"].`);
59
+
60
+ return undefined;
61
+ }
62
+
63
+ const names: string[] = [];
64
+
65
+ value.forEach((item: unknown, index) => {
66
+ const itemPath = `${path}.${key}[${String(index)}]`;
67
+
68
+ if (typeof item !== 'string' || !item) {
69
+ report(itemPath, 'must be a non-empty string.');
70
+ } else if (names.includes(item)) {
71
+ report(itemPath, `"${item}" is listed twice.`);
72
+ } else {
73
+ names.push(item);
74
+ }
75
+ });
76
+
77
+ return names;
78
+ }
79
+
80
+ export function checkKeys(
81
+ object: Record<string, unknown>,
82
+ allowed: readonly string[],
83
+ path: string,
84
+ report: Report
85
+ ): void {
86
+ for (const key of Object.keys(object)) {
87
+ if (!allowed.includes(key)) {
88
+ report(
89
+ path ? `${path}.${key}` : key,
90
+ `is not a known key${suggest(key, allowed)}. The keys are: ${allowed.map(name => `"${name}"`).join(', ')}.`
91
+ );
92
+ }
93
+ }
94
+ }
95
+
96
+ export function isRecord(value: unknown): value is Record<string, unknown> {
97
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
98
+ }
package/src/suggest.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Returns ` Did you mean "x"?` for the closest candidate, when it is close enough to be a typo.
3
+ */
4
+ export function suggest(name: string, candidates: readonly string[]): string {
5
+ let best: string | undefined;
6
+ let bestDistance = 3;
7
+
8
+ for (const candidate of candidates) {
9
+ const distance = editDistance(name.toLowerCase(), candidate.toLowerCase());
10
+
11
+ if (distance < bestDistance) {
12
+ best = candidate;
13
+ bestDistance = distance;
14
+ }
15
+ }
16
+
17
+ return best ? ` (did you mean "${best}"?)` : '';
18
+ }
19
+
20
+ function editDistance(a: string, b: string): number {
21
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
22
+
23
+ for (let i = 1; i <= a.length; i++) {
24
+ const current = [i];
25
+
26
+ for (let j = 1; j <= b.length; j++) {
27
+ current[j] = Math.min(
28
+ (previous[j] ?? 0) + 1,
29
+ (current[j - 1] ?? 0) + 1,
30
+ (previous[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1)
31
+ );
32
+ }
33
+
34
+ previous = current;
35
+ }
36
+
37
+ return previous[b.length] ?? 0;
38
+ }
package/src/types.ts ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The kinds of elements of an element schema:
3
+ *
4
+ * * `empty`: has no content, like an image or a line break. It is an object: selected as a whole.
5
+ * * `block`: can contain text and inline elements, like a paragraph or a heading. With `isInline`, it is an inline
6
+ * element with text, like a bold with an `id`.
7
+ *
8
+ * * `container`: cannot contain text; holds blocks, empty block elements and other containers, like a block quote or a
9
+ * note.
10
+ *
11
+ * Every type can be inline (see {@link ElementSchemaElement#isInline}): the type says what the element holds, `isInline`
12
+ * where and how it is laid out. Inline elements are allowed only in blocks.
13
+ */
14
+ export type ElementSchemaType = 'empty' | 'block' | 'container';
15
+
16
+ /**
17
+ * The kind of an element: what it is, and whether it is inline: laid out in a line with its siblings, instead of on a
18
+ * line of its own. Any type can be inline.
19
+ */
20
+ export interface ElementSchemaKind {
21
+ type: ElementSchemaType;
22
+ isInline: boolean;
23
+ }
24
+
25
+ /**
26
+ * The definition of one element of an element schema.
27
+ */
28
+ export interface ElementSchemaElement {
29
+ /**
30
+ * What the element is: `'empty'`, `'block'` or `'container'`.
31
+ */
32
+ type: ElementSchemaType;
33
+
34
+ /**
35
+ * Whether the element is inline: it is about the position and the layout of the element, not about text. An inline
36
+ * element is laid out in a line with its siblings, where a block-level one has a line of its own. Any type can be:
37
+ *
38
+ * * `empty`: like an inline image, or a line break;
39
+ * * `block`: an element with text in a line of text, like a bold that is an element, with attributes;
40
+ * * `container`: a box that is not typable, in a line with other boxes, e.g. several in a block.
41
+ *
42
+ * Inline elements are allowed only in blocks: by default, where text is. A block holds text and any number of inline
43
+ * elements; containers and the root hold no inline elements. Default: `false`.
44
+ */
45
+ isInline?: boolean;
46
+
47
+ /**
48
+ * The elements allowed inside this one, in addition to what its type allows: names of elements of the schema, or of
49
+ * elements of other plugins, and `'$text'`. As in CKEditor's `allowChildren`.
50
+ *
51
+ * What a type allows by default: a `block` has text and inline elements; a `container` has blocks, containers and
52
+ * `empty` block-level elements.
53
+ */
54
+ allowChildren?: string[];
55
+
56
+ /**
57
+ * The elements this one is allowed in, in addition to what its type allows: names of elements, and `'$root'`. As in
58
+ * CKEditor's `allowIn`.
59
+ *
60
+ * Unlike in CKEditor, when it is given it **replaces** the default places (a block-level element is allowed in the
61
+ * root and in containers; an inline element, where text is), so that an element can be restricted to its parent,
62
+ * e.g. a row to a table.
63
+ */
64
+ allowIn?: string[];
65
+
66
+ /**
67
+ * The attributes the element can have, as in CKEditor's `allowAttributes`.
68
+ */
69
+ allowAttributes?: string[];
70
+
71
+ /**
72
+ * The tag of the element in the editing view, e.g. `'section'`, when the converters of the schema are on (the
73
+ * `elementSchemaConverters` option). The data has `div` or `span`, whatever it is. Default: the tag of the data.
74
+ */
75
+ editingTag?: string;
76
+ }
77
+
78
+ /**
79
+ * An element schema: the definitions of the elements, by name.
80
+ */
81
+ export type ElementSchemaDefinition = Record<string, ElementSchemaElement>;
82
+
83
+ /**
84
+ * What the `elementSchema` option accepts: an {@link ElementSchemaDefinition}, or a JSON file imported as it is, whose
85
+ * strings TypeScript does not know are the allowed ones, and whose arrays are not known to be mutable. Either way, it is
86
+ * validated when the editor is created.
87
+ */
88
+ export type ElementSchemaInput = Record<
89
+ string,
90
+ {
91
+ [Key in keyof ElementSchemaElement]: Key extends 'type'
92
+ ? string
93
+ : ElementSchemaElement[Key] extends string[] | undefined
94
+ ? readonly string[] | undefined
95
+ : ElementSchemaElement[Key];
96
+ }
97
+ >;
98
+
99
+ /**
100
+ * A problem of an element schema.
101
+ */
102
+ export interface ElementSchemaIssue {
103
+ /**
104
+ * Where the problem is, e.g. `note.allowChildren[1]`.
105
+ */
106
+ path: string;
107
+
108
+ /**
109
+ * What is wrong, and what to do about it.
110
+ */
111
+ message: string;
112
+ }
113
+
114
+ /**
115
+ * The options of {@link validateElementSchema}.
116
+ */
117
+ export interface ValidateElementSchemaOptions {
118
+ /**
119
+ * Elements that exist outside the schema, e.g. `paragraph` of the `Paragraph` plugin, by name. The schema can refer to
120
+ * them, but cannot declare them. Without them, the schema can refer only to its own elements.
121
+ */
122
+ external?: Readonly<Record<string, ElementSchemaKind>>;
123
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The part of the element schema that needs no editor and no browser: the validation, the compiling to schema
3
+ * definitions, the error and the types. It is in its own module, so that it can be used in Node.js, e.g. to validate a
4
+ * schema file in a test or in CI: the main entry loads CKEditor, which needs a DOM.
5
+ *
6
+ * @module ckeditor5-elements-schema/validate
7
+ */
8
+
9
+ export { validateElementSchema } from './validateElementSchema.js';
10
+ export { compileElementSchema, type CompiledElementSchema } from './compileElementSchema.js';
11
+ export { default as ElementSchemaError } from './elementSchemaError.js';
12
+ export type {
13
+ ElementSchemaDefinition,
14
+ ElementSchemaElement,
15
+ ElementSchemaInput,
16
+ ElementSchemaIssue,
17
+ ElementSchemaKind,
18
+ ElementSchemaType,
19
+ ValidateElementSchemaOptions
20
+ } from './types.js';