@byline/richtext-lexical 3.0.2 → 3.1.1

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.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ /**
9
+ * Config-only surface for `@byline/richtext-lexical`.
10
+ *
11
+ * This entry is intentionally **light**: it carries the `lexicalEditor`
12
+ * registration factory (which dynamic-imports the editor runtime on first
13
+ * mount), the config types, the built-in extension **names**, and the
14
+ * light toolbar-authoring primitives — but NOT `RichTextField` /
15
+ * `EditorField` / `Nodes` or the heavy *content* extension classes
16
+ * (Table, InlineImage, Admonition, …), all of which statically pull React
17
+ * plugins, nodes, and the Lexical core. Import from here at registration
18
+ * sites (e.g. an admin/client config that you want to evaluate eagerly)
19
+ * so referencing the editor doesn't drag the editor module graph into the
20
+ * importing bundle.
21
+ *
22
+ * To toggle a built-in extension without importing its heavy class, use
23
+ * the name-based form of `extensions.remove/has/replace` with
24
+ * `builtInExtensions.*`:
25
+ *
26
+ * ```ts
27
+ * import { builtInExtensions, lexicalEditor } from '@byline/richtext-lexical/config'
28
+ *
29
+ * lexicalEditor((c) => {
30
+ * c.extensions.remove(builtInExtensions.FloatingTextFormat)
31
+ * return c
32
+ * })
33
+ * ```
34
+ *
35
+ * Need an actual extension class (to `.add(...)` it, or pass it to
36
+ * `configExtension`)? Import it from the `.` barrel instead — and accept
37
+ * that doing so pulls that extension's runtime into the bundle.
38
+ */
39
+ export { type BuiltInExtensionName, builtInExtensions, } from './field/config/built-in-extension-names';
40
+ export { type BylineToolbarConfig, BylineToolbarExtension, type BylineToolbarItem, type BylineToolbarPlacement, selectToolbarItems, } from './field/extensions/byline-toolbar';
41
+ export { ToolbarActiveEditorProvider, useToolbarActiveEditor, } from './field/plugins/toolbar-plugin/toolbar-active-editor';
42
+ export { lexicalEditor } from './lexical-editor';
43
+ export type { ExtensionsList } from './field/config/extensions-list';
44
+ export type { EditorConfig, EditorSettings, EditorSettingsOverride } from './field/config/types';
45
+ export type { LexicalEditorConfigureInput } from './lexical-editor';
package/dist/config.js ADDED
@@ -0,0 +1,4 @@
1
+ export { builtInExtensions } from "./field/config/built-in-extension-names.js";
2
+ export { BylineToolbarExtension, selectToolbarItems } from "./field/extensions/byline-toolbar/index.js";
3
+ export { ToolbarActiveEditorProvider, useToolbarActiveEditor } from "./field/plugins/toolbar-plugin/toolbar-active-editor.js";
4
+ export { lexicalEditor } from "./lexical-editor.js";
@@ -0,0 +1,51 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ /**
9
+ * The `name` of every built-in extension, as a plain string map.
10
+ *
11
+ * This module is **React-free and runtime-free** — it carries only the
12
+ * identifying strings, never the extension classes (which statically
13
+ * import their React plugins / nodes). That lets config-only code paths
14
+ * (the `@byline/richtext-lexical/config` subpath) reference a built-in
15
+ * for `extensions.remove(...)` / `.has(...)` / `.replace(...)` without
16
+ * dragging the editor runtime into the importing bundle.
17
+ *
18
+ * ```ts
19
+ * import { builtInExtensions, lexicalEditor } from '@byline/richtext-lexical/config'
20
+ *
21
+ * lexicalEditor((c) => {
22
+ * c.extensions.remove(builtInExtensions.FloatingTextFormat)
23
+ * return c
24
+ * })
25
+ * ```
26
+ *
27
+ * Each value MUST stay in sync with the corresponding extension's
28
+ * `defineExtension({ name })` — they share the `@byline/richtext-lexical/*`
29
+ * convention (suffix === map key). `built-in-extension-names.test.node.ts`
30
+ * guards this module's own integrity (convention, uniqueness); the mirror
31
+ * against the live extension `name`s is exercised by the editor's
32
+ * jsdom/integration coverage.
33
+ */
34
+ export declare const builtInExtensions: {
35
+ readonly Admonition: "@byline/richtext-lexical/Admonition";
36
+ readonly AutoEmbed: "@byline/richtext-lexical/AutoEmbed";
37
+ readonly AutoLink: "@byline/richtext-lexical/AutoLink";
38
+ readonly CodeHighlight: "@byline/richtext-lexical/CodeHighlight";
39
+ readonly FloatingTextFormat: "@byline/richtext-lexical/FloatingTextFormat";
40
+ readonly FloatingUI: "@byline/richtext-lexical/FloatingUI";
41
+ readonly HorizontalRule: "@byline/richtext-lexical/HorizontalRule";
42
+ readonly InlineImage: "@byline/richtext-lexical/InlineImage";
43
+ readonly Layout: "@byline/richtext-lexical/Layout";
44
+ readonly Link: "@byline/richtext-lexical/Link";
45
+ readonly Table: "@byline/richtext-lexical/Table";
46
+ readonly Toolbar: "@byline/richtext-lexical/Toolbar";
47
+ readonly Vimeo: "@byline/richtext-lexical/Vimeo";
48
+ readonly YouTube: "@byline/richtext-lexical/YouTube";
49
+ };
50
+ /** Union of the built-in extension `name` strings. */
51
+ export type BuiltInExtensionName = (typeof builtInExtensions)[keyof typeof builtInExtensions];
@@ -0,0 +1,17 @@
1
+ const builtInExtensions = {
2
+ Admonition: '@byline/richtext-lexical/Admonition',
3
+ AutoEmbed: '@byline/richtext-lexical/AutoEmbed',
4
+ AutoLink: '@byline/richtext-lexical/AutoLink',
5
+ CodeHighlight: '@byline/richtext-lexical/CodeHighlight',
6
+ FloatingTextFormat: '@byline/richtext-lexical/FloatingTextFormat',
7
+ FloatingUI: '@byline/richtext-lexical/FloatingUI',
8
+ HorizontalRule: '@byline/richtext-lexical/HorizontalRule',
9
+ InlineImage: '@byline/richtext-lexical/InlineImage',
10
+ Layout: '@byline/richtext-lexical/Layout',
11
+ Link: '@byline/richtext-lexical/Link',
12
+ Table: '@byline/richtext-lexical/Table',
13
+ Toolbar: '@byline/richtext-lexical/Toolbar',
14
+ Vimeo: '@byline/richtext-lexical/Vimeo',
15
+ YouTube: '@byline/richtext-lexical/YouTube'
16
+ };
17
+ export { builtInExtensions };
@@ -22,12 +22,19 @@ export declare class ExtensionsList {
22
22
  /** Append `extension` to the end of the list. */
23
23
  add(extension: AnyLexicalExtensionArgument): this;
24
24
  /**
25
- * Remove every entry whose name matches `extension.name`. No-op when
26
- * the extension isn't present.
25
+ * Remove every entry whose name matches the target. Accepts either an
26
+ * extension object (matched by `.name`) or the name string directly —
27
+ * the string form lets config-only code remove a built-in via
28
+ * `builtInExtensions.*` without importing the heavy extension class.
29
+ * No-op when the extension isn't present.
27
30
  */
28
- remove(extension: AnyLexicalExtension): this;
29
- /** Replace `oldExtension` with `newExtension`, preserving position. */
30
- replace(oldExtension: AnyLexicalExtension, newExtension: AnyLexicalExtensionArgument): this;
31
+ remove(extension: AnyLexicalExtension | string): this;
32
+ /**
33
+ * Replace `oldExtension` with `newExtension`, preserving position.
34
+ * `oldExtension` may be an extension object or its name string; the
35
+ * replacement must be a real extension argument.
36
+ */
37
+ replace(oldExtension: AnyLexicalExtension | string, newExtension: AnyLexicalExtensionArgument): this;
31
38
  /**
32
39
  * Re-wrap an existing extension entry with a fresh `configExtension`
33
40
  * binding the supplied config. If the extension isn't present, it is
@@ -35,8 +42,11 @@ export declare class ExtensionsList {
35
42
  * is replaced rather than nested.
36
43
  */
37
44
  configure<Extension extends AnyLexicalExtension>(extension: Extension, config: Partial<LexicalExtensionConfig<Extension>>): this;
38
- /** True when an entry with the same `name` is present. */
39
- has(extension: AnyLexicalExtension): boolean;
45
+ /**
46
+ * True when an entry with the same `name` is present. Accepts an
47
+ * extension object or its name string.
48
+ */
49
+ has(extension: AnyLexicalExtension | string): boolean;
40
50
  /** Independent copy — safe to hand to a configure callback. */
41
51
  clone(): ExtensionsList;
42
52
  /** The list as a plain array, ready for `defineExtension({ dependencies })`. */
@@ -5,12 +5,12 @@ class ExtensionsList {
5
5
  return this;
6
6
  }
7
7
  remove(extension) {
8
- const targetName = extension.name;
8
+ const targetName = resolveTargetName(extension);
9
9
  this.items = this.items.filter((item)=>extensionName(item) !== targetName);
10
10
  return this;
11
11
  }
12
12
  replace(oldExtension, newExtension) {
13
- const targetName = oldExtension.name;
13
+ const targetName = resolveTargetName(oldExtension);
14
14
  let replaced = false;
15
15
  this.items = this.items.map((item)=>{
16
16
  if (!replaced && extensionName(item) === targetName) {
@@ -27,7 +27,7 @@ class ExtensionsList {
27
27
  return this.replace(extension, wrapped);
28
28
  }
29
29
  has(extension) {
30
- const targetName = extension.name;
30
+ const targetName = resolveTargetName(extension);
31
31
  return this.items.some((item)=>extensionName(item) === targetName);
32
32
  }
33
33
  clone() {
@@ -44,6 +44,9 @@ class ExtensionsList {
44
44
  ];
45
45
  }
46
46
  }
47
+ function resolveTargetName(target) {
48
+ return 'string' == typeof target ? target : target.name;
49
+ }
47
50
  function extensionName(item) {
48
51
  if (Array.isArray(item)) {
49
52
  const head = item[0];
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type { SerializedEditor, SerializedEditorState, SerializedElementNode, SerializedLexicalNode, SerializedRootNode, SerializedTextNode, } from 'lexical';
2
+ export { type BuiltInExtensionName, builtInExtensions, } from './field/config/built-in-extension-names';
2
3
  export { defaultEditorConfig } from './field/config/default';
3
4
  export { defaultClientEditorConfig, defaultExtensionsArray, defaultExtensionsList, } from './field/config/default-extensions';
4
5
  export { ExtensionsList } from './field/config/extensions-list';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { builtInExtensions } from "./field/config/built-in-extension-names.js";
1
2
  export { defaultEditorConfig } from "./field/config/default.js";
2
3
  export { defaultClientEditorConfig, defaultExtensionsArray, defaultExtensionsList } from "./field/config/default-extensions.js";
3
4
  export { ExtensionsList } from "./field/config/extensions-list.js";
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "license": "MPL-2.0",
6
- "version": "3.0.2",
6
+ "version": "3.1.1",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -42,6 +42,12 @@
42
42
  "main": "./dist/index.js",
43
43
  "default": "./dist/index.js"
44
44
  },
45
+ "./config": {
46
+ "types": "./dist/config.d.ts",
47
+ "import": "./dist/config.js",
48
+ "main": "./dist/config.js",
49
+ "default": "./dist/config.js"
50
+ },
45
51
  "./server": {
46
52
  "types": "./dist/server.d.ts",
47
53
  "import": "./dist/server.js",
@@ -72,10 +78,10 @@
72
78
  "npm-run-all": "^4.1.5",
73
79
  "prism-react-renderer": "^2.4.1",
74
80
  "react-error-boundary": "^6.1.1",
75
- "@byline/admin": "3.0.2",
76
- "@byline/client": "3.0.2",
77
- "@byline/ui": "3.0.2",
78
- "@byline/core": "3.0.2"
81
+ "@byline/client": "3.1.1",
82
+ "@byline/ui": "3.1.1",
83
+ "@byline/core": "3.1.1",
84
+ "@byline/admin": "3.1.1"
79
85
  },
80
86
  "peerDependencies": {
81
87
  "react": "^19.0.0",
package/src/config.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Config-only surface for `@byline/richtext-lexical`.
11
+ *
12
+ * This entry is intentionally **light**: it carries the `lexicalEditor`
13
+ * registration factory (which dynamic-imports the editor runtime on first
14
+ * mount), the config types, the built-in extension **names**, and the
15
+ * light toolbar-authoring primitives — but NOT `RichTextField` /
16
+ * `EditorField` / `Nodes` or the heavy *content* extension classes
17
+ * (Table, InlineImage, Admonition, …), all of which statically pull React
18
+ * plugins, nodes, and the Lexical core. Import from here at registration
19
+ * sites (e.g. an admin/client config that you want to evaluate eagerly)
20
+ * so referencing the editor doesn't drag the editor module graph into the
21
+ * importing bundle.
22
+ *
23
+ * To toggle a built-in extension without importing its heavy class, use
24
+ * the name-based form of `extensions.remove/has/replace` with
25
+ * `builtInExtensions.*`:
26
+ *
27
+ * ```ts
28
+ * import { builtInExtensions, lexicalEditor } from '@byline/richtext-lexical/config'
29
+ *
30
+ * lexicalEditor((c) => {
31
+ * c.extensions.remove(builtInExtensions.FloatingTextFormat)
32
+ * return c
33
+ * })
34
+ * ```
35
+ *
36
+ * Need an actual extension class (to `.add(...)` it, or pass it to
37
+ * `configExtension`)? Import it from the `.` barrel instead — and accept
38
+ * that doing so pulls that extension's runtime into the bundle.
39
+ */
40
+
41
+ export {
42
+ type BuiltInExtensionName,
43
+ builtInExtensions,
44
+ } from './field/config/built-in-extension-names'
45
+ // Light extension-authoring primitives — the toolbar coordination
46
+ // extension, its selectors/types, and the active-editor hook. These carry
47
+ // no React plugin/node runtime (only `lexical` + a React context), so a
48
+ // third-party extension (e.g. `@byline/ai`'s Lexical extension) can
49
+ // declare a toolbar contribution and dispatch commands without pulling
50
+ // the editor's content runtime into its bundle.
51
+ export {
52
+ type BylineToolbarConfig,
53
+ BylineToolbarExtension,
54
+ type BylineToolbarItem,
55
+ type BylineToolbarPlacement,
56
+ selectToolbarItems,
57
+ } from './field/extensions/byline-toolbar'
58
+ export {
59
+ ToolbarActiveEditorProvider,
60
+ useToolbarActiveEditor,
61
+ } from './field/plugins/toolbar-plugin/toolbar-active-editor'
62
+ export { lexicalEditor } from './lexical-editor'
63
+ export type { ExtensionsList } from './field/config/extensions-list'
64
+ export type { EditorConfig, EditorSettings, EditorSettingsOverride } from './field/config/types'
65
+ export type { LexicalEditorConfigureInput } from './lexical-editor'
@@ -0,0 +1,44 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { describe, expect, it } from 'vitest'
10
+
11
+ import { builtInExtensions } from './built-in-extension-names'
12
+
13
+ /**
14
+ * Node-safe self-consistency checks for the `builtInExtensions` name map.
15
+ *
16
+ * The map deliberately carries strings only (no extension imports), so
17
+ * this test cannot diff against the live `defineExtension({ name })`
18
+ * values without dragging the React-heavy editor graph into a node test.
19
+ * Instead it guards the map's own integrity — the `@byline/richtext-lexical/*`
20
+ * convention, uniqueness, and that each key matches the suffix of its
21
+ * value. Each value must still mirror the corresponding extension's
22
+ * `name`; that mirror is asserted by the editor's jsdom/integration
23
+ * coverage, not here.
24
+ */
25
+ describe('builtInExtensions name map', () => {
26
+ const entries = Object.entries(builtInExtensions)
27
+
28
+ it('namespaces every value under @byline/richtext-lexical/', () => {
29
+ for (const [, value] of entries) {
30
+ expect(value).toMatch(/^@byline\/richtext-lexical\/[A-Za-z]+$/)
31
+ }
32
+ })
33
+
34
+ it('uses each value exactly once', () => {
35
+ const values = entries.map(([, value]) => value)
36
+ expect(new Set(values).size).toBe(values.length)
37
+ })
38
+
39
+ it('names each key after the suffix of its value', () => {
40
+ for (const [key, value] of entries) {
41
+ expect(value).toBe(`@byline/richtext-lexical/${key}`)
42
+ }
43
+ })
44
+ })
@@ -0,0 +1,53 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * The `name` of every built-in extension, as a plain string map.
11
+ *
12
+ * This module is **React-free and runtime-free** — it carries only the
13
+ * identifying strings, never the extension classes (which statically
14
+ * import their React plugins / nodes). That lets config-only code paths
15
+ * (the `@byline/richtext-lexical/config` subpath) reference a built-in
16
+ * for `extensions.remove(...)` / `.has(...)` / `.replace(...)` without
17
+ * dragging the editor runtime into the importing bundle.
18
+ *
19
+ * ```ts
20
+ * import { builtInExtensions, lexicalEditor } from '@byline/richtext-lexical/config'
21
+ *
22
+ * lexicalEditor((c) => {
23
+ * c.extensions.remove(builtInExtensions.FloatingTextFormat)
24
+ * return c
25
+ * })
26
+ * ```
27
+ *
28
+ * Each value MUST stay in sync with the corresponding extension's
29
+ * `defineExtension({ name })` — they share the `@byline/richtext-lexical/*`
30
+ * convention (suffix === map key). `built-in-extension-names.test.node.ts`
31
+ * guards this module's own integrity (convention, uniqueness); the mirror
32
+ * against the live extension `name`s is exercised by the editor's
33
+ * jsdom/integration coverage.
34
+ */
35
+ export const builtInExtensions = {
36
+ Admonition: '@byline/richtext-lexical/Admonition',
37
+ AutoEmbed: '@byline/richtext-lexical/AutoEmbed',
38
+ AutoLink: '@byline/richtext-lexical/AutoLink',
39
+ CodeHighlight: '@byline/richtext-lexical/CodeHighlight',
40
+ FloatingTextFormat: '@byline/richtext-lexical/FloatingTextFormat',
41
+ FloatingUI: '@byline/richtext-lexical/FloatingUI',
42
+ HorizontalRule: '@byline/richtext-lexical/HorizontalRule',
43
+ InlineImage: '@byline/richtext-lexical/InlineImage',
44
+ Layout: '@byline/richtext-lexical/Layout',
45
+ Link: '@byline/richtext-lexical/Link',
46
+ Table: '@byline/richtext-lexical/Table',
47
+ Toolbar: '@byline/richtext-lexical/Toolbar',
48
+ Vimeo: '@byline/richtext-lexical/Vimeo',
49
+ YouTube: '@byline/richtext-lexical/YouTube',
50
+ } as const
51
+
52
+ /** Union of the built-in extension `name` strings. */
53
+ export type BuiltInExtensionName = (typeof builtInExtensions)[keyof typeof builtInExtensions]
@@ -37,18 +37,28 @@ export class ExtensionsList {
37
37
  }
38
38
 
39
39
  /**
40
- * Remove every entry whose name matches `extension.name`. No-op when
41
- * the extension isn't present.
40
+ * Remove every entry whose name matches the target. Accepts either an
41
+ * extension object (matched by `.name`) or the name string directly —
42
+ * the string form lets config-only code remove a built-in via
43
+ * `builtInExtensions.*` without importing the heavy extension class.
44
+ * No-op when the extension isn't present.
42
45
  */
43
- remove(extension: AnyLexicalExtension): this {
44
- const targetName = extension.name
46
+ remove(extension: AnyLexicalExtension | string): this {
47
+ const targetName = resolveTargetName(extension)
45
48
  this.items = this.items.filter((item) => extensionName(item) !== targetName)
46
49
  return this
47
50
  }
48
51
 
49
- /** Replace `oldExtension` with `newExtension`, preserving position. */
50
- replace(oldExtension: AnyLexicalExtension, newExtension: AnyLexicalExtensionArgument): this {
51
- const targetName = oldExtension.name
52
+ /**
53
+ * Replace `oldExtension` with `newExtension`, preserving position.
54
+ * `oldExtension` may be an extension object or its name string; the
55
+ * replacement must be a real extension argument.
56
+ */
57
+ replace(
58
+ oldExtension: AnyLexicalExtension | string,
59
+ newExtension: AnyLexicalExtensionArgument
60
+ ): this {
61
+ const targetName = resolveTargetName(oldExtension)
52
62
  let replaced = false
53
63
  this.items = this.items.map((item) => {
54
64
  if (!replaced && extensionName(item) === targetName) {
@@ -75,9 +85,12 @@ export class ExtensionsList {
75
85
  return this.replace(extension, wrapped)
76
86
  }
77
87
 
78
- /** True when an entry with the same `name` is present. */
79
- has(extension: AnyLexicalExtension): boolean {
80
- const targetName = extension.name
88
+ /**
89
+ * True when an entry with the same `name` is present. Accepts an
90
+ * extension object or its name string.
91
+ */
92
+ has(extension: AnyLexicalExtension | string): boolean {
93
+ const targetName = resolveTargetName(extension)
81
94
  return this.items.some((item) => extensionName(item) === targetName)
82
95
  }
83
96
 
@@ -92,6 +105,11 @@ export class ExtensionsList {
92
105
  }
93
106
  }
94
107
 
108
+ /** Resolve a remove/has/replace target to its comparison name. */
109
+ function resolveTargetName(target: AnyLexicalExtension | string): string {
110
+ return typeof target === 'string' ? target : target.name
111
+ }
112
+
95
113
  function extensionName(item: AnyLexicalExtensionArgument): string | undefined {
96
114
  if (Array.isArray(item)) {
97
115
  const head = item[0]
package/src/index.ts CHANGED
@@ -10,6 +10,10 @@ export type {
10
10
  SerializedTextNode,
11
11
  } from 'lexical'
12
12
 
13
+ export {
14
+ type BuiltInExtensionName,
15
+ builtInExtensions,
16
+ } from './field/config/built-in-extension-names'
13
17
  export { defaultEditorConfig } from './field/config/default'
14
18
  export {
15
19
  defaultClientEditorConfig,