@apollohg/react-native-rich-text-editor 2.0.0 → 2.0.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.
Files changed (161) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/android/build.gradle +3 -0
  4. package/app.plugin.js +42 -39
  5. package/dist/AtomHost.js +18 -11
  6. package/dist/EditorAddon.js +4 -2
  7. package/dist/EditorAddonNormalization.js +17 -9
  8. package/dist/EditorMentionThemeNormalization.js +6 -3
  9. package/dist/EditorMentionThemeValidation.js +28 -11
  10. package/dist/EditorStyleSheetNormalization.js +84 -37
  11. package/dist/EditorTheme.d.ts +4 -0
  12. package/dist/EditorTheme.js +5 -3
  13. package/dist/EditorToolbarComponent.d.ts +1 -1
  14. package/dist/EditorToolbarComponent.js +1 -1
  15. package/dist/EditorToolbarRegistry.js +5 -5
  16. package/dist/EditorToolbarTypes.d.ts +12 -12
  17. package/dist/EditorToolbarVisuals.js +1 -1
  18. package/dist/ExternalTextComposition.js +89 -43
  19. package/dist/ImageLoadingPolicy.d.ts +1 -1
  20. package/dist/NativeEditorBoundaryError.d.ts +7 -7
  21. package/dist/NativeEditorBoundaryError.js +9 -5
  22. package/dist/NativeEditorCollaborationTransport.d.ts +7 -7
  23. package/dist/NativeEditorCollaborationTransport.js +28 -15
  24. package/dist/NativeEditorCreateConfig.js +11 -6
  25. package/dist/NativeEditorCreateJson.js +51 -25
  26. package/dist/NativeEditorCreateValidation.js +11 -6
  27. package/dist/NativeEditorDocumentBridge.d.ts +1 -1
  28. package/dist/NativeEditorDocumentBridge.js +32 -18
  29. package/dist/NativeEditorDocumentHandle.d.ts +4 -4
  30. package/dist/NativeEditorDocumentHandle.js +1 -1
  31. package/dist/NativeEditorLocalAwareness.js +25 -13
  32. package/dist/NativeEditorNativeModule.d.ts +8 -1
  33. package/dist/NativeEditorNativeModule.js +7 -5
  34. package/dist/NativeEditorRenderNormalization.js +66 -25
  35. package/dist/NativeEditorResultNormalization.js +53 -29
  36. package/dist/NativeEditorTypes.d.ts +10 -10
  37. package/dist/NativeEditorV2Decimal.js +6 -3
  38. package/dist/NativeProseViewer.js +11 -3
  39. package/dist/ResourceLimits.d.ts +2 -2
  40. package/dist/ResourceLimits.js +7 -2
  41. package/dist/RichTextEditor.js +1 -1
  42. package/dist/RichTextEditorNativeTypes.d.ts +1 -1
  43. package/dist/RichTextEditorNativeView.d.ts +1 -3
  44. package/dist/RichTextEditorSerialization.js +24 -14
  45. package/dist/RichTextEditorTypes.d.ts +1 -1
  46. package/dist/YjsCollaboration.d.ts +7 -7
  47. package/dist/YjsCollaboration.js +42 -26
  48. package/dist/addons.d.ts +4 -4
  49. package/dist/addons.js +3 -3
  50. package/dist/atomInstances.js +6 -3
  51. package/dist/atomUpdates.js +7 -4
  52. package/dist/atoms.js +20 -12
  53. package/dist/attributeValidation.js +55 -24
  54. package/dist/contentExpression.js +80 -42
  55. package/dist/listMarkers.d.ts +1 -1
  56. package/dist/listMarkers.js +1 -1
  57. package/dist/schemaDefinition.d.ts +1 -1
  58. package/dist/schemaDefinition.js +23 -14
  59. package/dist/schemaDocument.js +1 -1
  60. package/dist/schemaDocumentConstruction.js +25 -14
  61. package/dist/schemaNormalization.js +51 -29
  62. package/dist/schemaPresets.d.ts +1 -1
  63. package/dist/schemaPresets.js +9 -4
  64. package/dist/schemaResolution.js +46 -25
  65. package/dist/specs/PreparedProseViewerNativeComponent.d.ts +4 -0
  66. package/dist/useEditorToolbarInteractions.d.ts +2 -2
  67. package/dist/useEditorToolbarInteractions.js +45 -23
  68. package/dist/useEditorToolbarItems.d.ts +1 -1
  69. package/dist/useEditorToolbarItems.js +4 -3
  70. package/dist/useEditorToolbarPresentation.d.ts +3 -3
  71. package/dist/useEditorToolbarPresentation.js +4 -4
  72. package/dist/useEditorToolbarState.d.ts +1 -1
  73. package/dist/useEditorToolbarState.js +1 -1
  74. package/dist/useFocusPreservingFrames.js +11 -8
  75. package/dist/useNativeEditor.d.ts +1 -1
  76. package/dist/useNativeEditor.js +21 -11
  77. package/dist/useRichTextEditorCommands.d.ts +2 -2
  78. package/dist/useRichTextEditorCommands.js +59 -29
  79. package/dist/useRichTextEditorEvents.d.ts +3 -3
  80. package/dist/useRichTextEditorEvents.js +75 -32
  81. package/dist/useRichTextEditorMentions.d.ts +4 -4
  82. package/dist/useRichTextEditorMentions.js +48 -29
  83. package/dist/useRichTextEditorPresentation.d.ts +4 -4
  84. package/dist/useRichTextEditorPresentation.js +25 -23
  85. package/dist/useRichTextEditorState.d.ts +3 -3
  86. package/dist/useRichTextEditorState.js +20 -15
  87. package/dist/useRichTextEditorUpdates.d.ts +1 -1
  88. package/dist/useRichTextEditorUpdates.js +99 -41
  89. package/dist/useViewerAtoms.js +63 -36
  90. package/ios/EditorCore.xcframework/Info.plist +5 -5
  91. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
  92. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
  93. package/package.json +11 -3
  94. package/react-native.config.js +1 -1
  95. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  96. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  97. package/rust/android/x86/libeditor_core.so +0 -0
  98. package/rust/android/x86_64/libeditor_core.so +0 -0
  99. package/src/AtomHost.tsx +58 -28
  100. package/src/EditorAddon.ts +18 -8
  101. package/src/EditorAddonNormalization.ts +40 -11
  102. package/src/EditorMentionThemeNormalization.ts +15 -4
  103. package/src/EditorMentionThemeValidation.ts +51 -14
  104. package/src/EditorStyleSheet.ts +1 -0
  105. package/src/EditorStyleSheetNormalization.ts +195 -64
  106. package/src/EditorTheme.ts +13 -5
  107. package/src/EditorToolbarComponent.tsx +2 -1
  108. package/src/EditorToolbarRegistry.tsx +23 -13
  109. package/src/EditorToolbarTypes.tsx +12 -12
  110. package/src/EditorToolbarVisuals.tsx +6 -3
  111. package/src/EditorUpdateRevision.ts +1 -0
  112. package/src/ExternalTextComposition.ts +259 -49
  113. package/src/ImageLoadingPolicy.ts +3 -1
  114. package/src/NativeEditorBoundaryError.ts +38 -11
  115. package/src/NativeEditorCollaborationTransport.ts +108 -27
  116. package/src/NativeEditorCreateConfig.ts +92 -19
  117. package/src/NativeEditorCreateJson.ts +163 -26
  118. package/src/NativeEditorCreateValidation.ts +33 -12
  119. package/src/NativeEditorDocumentBridge.ts +111 -19
  120. package/src/NativeEditorDocumentHandle.ts +9 -4
  121. package/src/NativeEditorLocalAwareness.ts +70 -15
  122. package/src/NativeEditorNativeModule.ts +13 -9
  123. package/src/NativeEditorRenderNormalization.ts +128 -29
  124. package/src/NativeEditorResultNormalization.ts +143 -32
  125. package/src/NativeEditorTypes.ts +12 -11
  126. package/src/NativeEditorV2Decimal.ts +12 -3
  127. package/src/NativeProseViewer.tsx +46 -16
  128. package/src/ResourceLimits.ts +15 -8
  129. package/src/RichTextEditor.tsx +3 -1
  130. package/src/RichTextEditorNativeTypes.tsx +1 -1
  131. package/src/RichTextEditorNativeView.tsx +2 -8
  132. package/src/RichTextEditorSerialization.tsx +70 -24
  133. package/src/RichTextEditorTypes.tsx +1 -1
  134. package/src/YjsCollaboration.ts +143 -47
  135. package/src/addons.ts +12 -8
  136. package/src/atomInstances.ts +25 -5
  137. package/src/atomUpdates.ts +23 -6
  138. package/src/atoms.ts +85 -18
  139. package/src/attributeValidation.ts +97 -33
  140. package/src/contentExpression.ts +218 -63
  141. package/src/listMarkers.ts +1 -1
  142. package/src/schemaDefinition.ts +82 -20
  143. package/src/schemaDocument.ts +15 -1
  144. package/src/schemaDocumentConstruction.ts +80 -16
  145. package/src/schemaNormalization.ts +155 -41
  146. package/src/schemaPresets.ts +37 -31
  147. package/src/schemaResolution.ts +126 -27
  148. package/src/specs/PreparedProseViewerNativeComponent.ts +4 -2
  149. package/src/useEditorToolbarInteractions.tsx +85 -36
  150. package/src/useEditorToolbarItems.tsx +50 -22
  151. package/src/useEditorToolbarPresentation.tsx +81 -58
  152. package/src/useEditorToolbarState.tsx +8 -6
  153. package/src/useFocusPreservingFrames.ts +43 -15
  154. package/src/useNativeEditor.ts +111 -51
  155. package/src/useRichTextEditorCommands.tsx +127 -54
  156. package/src/useRichTextEditorEvents.tsx +144 -42
  157. package/src/useRichTextEditorMentions.tsx +118 -37
  158. package/src/useRichTextEditorPresentation.tsx +128 -114
  159. package/src/useRichTextEditorState.tsx +76 -40
  160. package/src/useRichTextEditorUpdates.tsx +207 -56
  161. package/src/useViewerAtoms.tsx +162 -66
@@ -10,14 +10,25 @@ export type SerializedEditorMentionTheme = Omit<EditorMentionTheme, 'node'> & {
10
10
  export function normalizeEditorMentionTheme(
11
11
  theme?: EditorMentionTheme
12
12
  ): SerializedEditorMentionTheme | undefined {
13
- if (theme === undefined) return undefined;
14
- if (!validEditorMentionTheme(theme)) throw new TypeError('Invalid mention theme');
15
- if (!theme.node) return theme;
13
+ if (theme === undefined) {
14
+ return undefined;
15
+ }
16
+
17
+ if (!validEditorMentionTheme(theme)) {
18
+ throw new TypeError('Invalid mention theme');
19
+ }
20
+
21
+ if (!theme.node) {
22
+ return theme;
23
+ }
24
+
16
25
  const node = theme.node as Record<string, unknown>;
17
26
  const { textColor, style: existingStyle, ...appearance } = node;
27
+
18
28
  const style = normalizeEditorTheme({
19
- mention: [{ color: textColor }, appearance, existingStyle],
29
+ mention: [ { color: textColor }, appearance, existingStyle ],
20
30
  }).styles?.mention;
31
+
21
32
  return {
22
33
  ...theme,
23
34
  node: { style },
@@ -5,12 +5,13 @@ import type {
5
5
  EditorMentionTheme,
6
6
  } from './EditorTheme';
7
7
  import { normalizeEditorTheme } from './EditorStyleSheetNormalization';
8
+
8
9
  function isPlainRecord(value: unknown): value is Record<string, unknown> {
9
10
  return value !== null && typeof value === 'object' && !Array.isArray(value);
10
11
  }
11
12
 
12
13
  function hasOnlyOwnKeys(value: Record<string, unknown>, fields: readonly string[]): boolean {
13
- return Reflect.ownKeys(value).every((key) => typeof key === 'string' && fields.includes(key));
14
+ return Reflect.ownKeys(value).every(key => typeof key === 'string' && fields.includes(key));
14
15
  }
15
16
 
16
17
  export const MENTION_NODE_STRING_FIELDS = [
@@ -51,7 +52,17 @@ export const MENTION_SUGGESTIONS_NUMBER_FIELDS = [
51
52
 
52
53
  export const EDITOR_MENTION_THEME_FONT_WEIGHTS: ReadonlySet<
53
54
  NonNullable<EditorMentionNodeTheme['fontWeight']>
54
- > = new Set(['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']);
55
+ > = new Set([ 'normal',
56
+ 'bold',
57
+ '100',
58
+ '200',
59
+ '300',
60
+ '400',
61
+ '500',
62
+ '600',
63
+ '700',
64
+ '800',
65
+ '900' ]);
55
66
 
56
67
  export const EDITOR_MENTION_THEME_FIELDS = [
57
68
  'node',
@@ -74,14 +85,20 @@ export function validMentionThemeSection(
74
85
  numberFields: readonly string[],
75
86
  extraFields: readonly string[]
76
87
  ): value is Record<string, unknown> {
77
- if (!isPlainRecord(value)) return false;
78
- if (!hasOnlyOwnKeys(value, [...stringFields, ...numberFields, ...extraFields])) return false;
88
+ if (!isPlainRecord(value)) {
89
+ return false;
90
+ }
91
+
92
+ if (!hasOnlyOwnKeys(value, [ ...stringFields, ...numberFields, ...extraFields ])) {
93
+ return false;
94
+ }
95
+
79
96
  return (
80
97
  stringFields.every(
81
- (field) => value[field] === undefined || typeof value[field] === 'string'
98
+ field => value[field] === undefined || typeof value[field] === 'string'
82
99
  ) &&
83
100
  numberFields.every(
84
- (field) =>
101
+ field =>
85
102
  value[field] === undefined ||
86
103
  (typeof value[field] === 'number' && Number.isFinite(value[field]))
87
104
  )
@@ -94,40 +111,60 @@ export function validEditorMentionTheme(value: unknown): value is EditorMentionT
94
111
  }
95
112
 
96
113
  const { node, suggestions } = value;
114
+
97
115
  if (node !== undefined) {
98
- if (!isPlainRecord(node)) return false;
116
+ if (!isPlainRecord(node)) {
117
+ return false;
118
+ }
119
+
99
120
  const { textColor, style, ...appearance } = node;
100
- if (textColor !== undefined && typeof textColor !== 'string') return false;
121
+
122
+ if (textColor !== undefined && typeof textColor !== 'string') {
123
+ return false;
124
+ }
125
+
101
126
  try {
102
- normalizeEditorTheme({ mention: [{ color: textColor }, appearance] });
127
+ normalizeEditorTheme({ mention: [ { color: textColor }, appearance ] });
128
+
103
129
  if (style !== undefined) {
104
- if (!isPlainRecord(style)) return false;
130
+ if (!isPlainRecord(style)) {
131
+ return false;
132
+ }
133
+
105
134
  normalizeEditorTheme({ mention: style });
106
135
  }
107
136
  } catch {
108
137
  return false;
109
138
  }
110
139
  }
111
- if (suggestions === undefined) return true;
140
+
141
+ if (suggestions === undefined) {
142
+ return true;
143
+ }
144
+
112
145
  if (
113
146
  !validMentionThemeSection(
114
147
  suggestions,
115
148
  MENTION_SUGGESTIONS_STRING_FIELDS,
116
149
  MENTION_SUGGESTIONS_NUMBER_FIELDS,
117
- ['option']
150
+ [ 'option' ]
118
151
  )
119
152
  ) {
120
153
  return false;
121
154
  }
122
155
 
123
156
  const option = suggestions.option;
124
- if (option === undefined) return true;
157
+
158
+ if (option === undefined) {
159
+ return true;
160
+ }
161
+
125
162
  return (
126
163
  validMentionThemeSection(
127
164
  option,
128
165
  MENTION_OPTION_STRING_FIELDS,
129
166
  MENTION_OPTION_NUMBER_FIELDS,
130
- ['fontWeight']
167
+ [ 'fontWeight' ]
131
168
  ) && validMentionFontWeight(option.fontWeight)
132
169
  );
133
170
  }
@@ -4,6 +4,7 @@ import { normalizeEditorTheme } from './EditorStyleSheetNormalization';
4
4
  export const EditorStyleSheet = {
5
5
  create<const T extends object>(styles: T & ExactEditorTheme<T>): T {
6
6
  normalizeEditorTheme(styles);
7
+
7
8
  return styles;
8
9
  },
9
10
  };
@@ -17,27 +17,32 @@ const typography = [
17
17
  'textDecorationColor',
18
18
  'textDecorationStyle',
19
19
  ];
20
- const sides = ['Top', 'Right', 'Bottom', 'Left'];
21
- const corners = ['TopLeft', 'TopRight', 'BottomRight', 'BottomLeft'];
20
+
21
+ const sides = [ 'Top', 'Right', 'Bottom', 'Left' ];
22
+ const corners = [ 'TopLeft', 'TopRight', 'BottomRight', 'BottomLeft' ];
23
+
22
24
  const border = [
23
25
  'borderWidth',
24
26
  'borderColor',
25
27
  'borderStyle',
26
28
  'borderRadius',
27
- ...sides.flatMap((side) => [`border${side}Width`, `border${side}Color`]),
28
- ...corners.map((corner) => `border${corner}Radius`),
29
+ ...sides.flatMap(side => [ `border${side}Width`, `border${side}Color` ]),
30
+ ...corners.map(corner => `border${corner}Radius`),
29
31
  ];
32
+
30
33
  const spacing = (prefix: string) => [
31
34
  prefix,
32
35
  `${prefix}Horizontal`,
33
36
  `${prefix}Vertical`,
34
- ...sides.map((side) => `${prefix}${side}`),
37
+ ...sides.map(side => `${prefix}${side}`),
35
38
  ];
36
- const surface = ['backgroundColor', ...border, ...spacing('padding')];
37
- const box = [...surface, ...spacing('margin')];
38
- const block = [...typography, ...box, 'textAlign'];
39
- const inline = [...typography, 'backgroundColor'];
40
- const checkbox = [...border, 'backgroundColor', 'checkColor'];
39
+
40
+ const surface = [ 'backgroundColor', ...border, ...spacing('padding') ];
41
+ const box = [ ...surface, ...spacing('margin') ];
42
+ const block = [ ...typography, ...box, 'textAlign' ];
43
+ const inline = [ ...typography, 'backgroundColor' ];
44
+ const checkbox = [ ...border, 'backgroundColor', 'checkColor' ];
45
+
41
46
  const fields: Record<keyof EditorStyleMap, readonly string[]> = {
42
47
  content: surface,
43
48
  text: typography,
@@ -50,32 +55,47 @@ const fields: Record<keyof EditorStyleMap, readonly string[]> = {
50
55
  h6: block,
51
56
  blockquote: block,
52
57
  codeBlock: block,
53
- bulletList: [...block, 'indent', 'baseIndentMultiplier'],
54
- orderedList: [...block, 'indent', 'baseIndentMultiplier'],
55
- taskList: [...block, 'indent', 'baseIndentMultiplier'],
58
+ bulletList: [ ...block, 'indent', 'baseIndentMultiplier' ],
59
+ orderedList: [ ...block, 'indent', 'baseIndentMultiplier' ],
60
+ taskList: [ ...block, 'indent', 'baseIndentMultiplier' ],
56
61
  listItem: block,
57
62
  taskItem: block,
58
- listMarker: ['color', 'scale', 'gap', 'ordered'],
59
- taskCheckbox: [...checkbox, 'size', 'gap', 'checked'],
60
- horizontalRule: [...border, ...spacing('margin'), 'backgroundColor', 'height'],
61
- image: [...box, 'resizeMode'],
63
+ listMarker: [ 'color', 'scale', 'gap', 'ordered' ],
64
+ taskCheckbox: [ ...checkbox, 'size', 'gap', 'checked' ],
65
+ horizontalRule: [ ...border, ...spacing('margin'), 'backgroundColor', 'height' ],
66
+ image: [ ...box, 'resizeMode' ],
62
67
  link: inline,
63
68
  inlineCode: inline,
64
69
  bold: inline,
65
70
  italic: inline,
66
71
  underline: inline,
67
72
  strike: inline,
68
- mention: [...inline, ...border],
73
+ mention: [ ...inline, ...border ],
69
74
  placeholder: typography,
70
75
  };
76
+
71
77
  const enums: Record<string, readonly string[]> = {
72
- fontStyle: ['normal', 'italic'],
73
- fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
74
- textAlign: ['auto', 'left', 'right', 'center', 'justify'],
75
- textDecorationLine: ['none', 'underline', 'line-through', 'underline line-through'],
76
- textDecorationStyle: ['solid', 'double', 'dotted', 'dashed'],
77
- borderStyle: ['solid', 'dashed', 'dotted'],
78
- resizeMode: ['contain', 'cover', 'stretch'],
78
+ fontStyle: [ 'normal', 'italic' ],
79
+ fontWeight: [ 'normal',
80
+ 'bold',
81
+ '100',
82
+ '200',
83
+ '300',
84
+ '400',
85
+ '500',
86
+ '600',
87
+ '700',
88
+ '800',
89
+ '900' ],
90
+ textAlign: [ 'auto',
91
+ 'left',
92
+ 'right',
93
+ 'center',
94
+ 'justify' ],
95
+ textDecorationLine: [ 'none', 'underline', 'line-through', 'underline line-through' ],
96
+ textDecorationStyle: [ 'solid', 'double', 'dotted', 'dashed' ],
97
+ borderStyle: [ 'solid', 'dashed', 'dotted' ],
98
+ resizeMode: [ 'contain', 'cover', 'stretch' ],
79
99
  };
80
100
 
81
101
  function invalid(path: string, reason: string): never {
@@ -83,66 +103,122 @@ function invalid(path: string, reason: string): never {
83
103
  }
84
104
 
85
105
  function record(value: unknown): value is Record<string, unknown> {
86
- if (value == null || typeof value !== 'object' || Array.isArray(value)) return false;
106
+ if (value == null || typeof value !== 'object' || Array.isArray(value)) {
107
+ return false;
108
+ }
109
+
87
110
  const prototype = Object.getPrototypeOf(value);
111
+
88
112
  return prototype === Object.prototype || prototype === null;
89
113
  }
90
114
 
91
115
  export function normalizeEditorColor(value: unknown, path: string): string {
92
- if (typeof value !== 'string') return invalid(path, 'expected a color string');
116
+ if (typeof value !== 'string') {
117
+ return invalid(path, 'expected a color string');
118
+ }
119
+
93
120
  const argb = processColor(value);
94
- if (typeof argb !== 'number') return invalid(path, 'unrecognized color');
121
+
122
+ if (typeof argb !== 'number') {
123
+ return invalid(path, 'unrecognized color');
124
+ }
125
+
95
126
  const rgba = ((argb << 8) | (argb >>> 24)) >>> 0;
127
+
96
128
  return `#${rgba.toString(16).padStart(8, '0')}`;
97
129
  }
98
130
 
99
131
  function orderedMarker(value: unknown, path: string): Record<string, unknown> {
100
- if (!record(value)) return invalid(path, 'expected an ordered marker object');
132
+ if (!record(value)) {
133
+ return invalid(path, 'expected an ordered marker object');
134
+ }
135
+
101
136
  const result: Record<string, unknown> = {};
102
- for (const [key, entry] of Object.entries(value)) {
103
- if (key !== 'schemes' && key !== 'suffix')
137
+
138
+ for (const [ key, entry ] of Object.entries(value)) {
139
+ if (key !== 'schemes' && key !== 'suffix') {
104
140
  invalid(`${path}.${key}`, 'unsupported property');
105
- if (entry === undefined) continue;
141
+ }
142
+
143
+ if (entry === undefined) {
144
+ continue;
145
+ }
146
+
106
147
  if (key === 'suffix') {
107
- if (entry !== '.' && entry !== ')') invalid(`${path}.${key}`, 'expected . or )');
148
+ if (entry !== '.' && entry !== ')') {
149
+ invalid(`${path}.${key}`, 'expected . or )');
150
+ }
151
+
108
152
  result[key] = entry;
109
153
  } else {
110
- const schemes = ['decimal', 'lowerAlpha', 'upperAlpha', 'lowerRoman', 'upperRoman'];
154
+ const schemes = [ 'decimal',
155
+ 'lowerAlpha',
156
+ 'upperAlpha',
157
+ 'lowerRoman',
158
+ 'upperRoman' ];
159
+
111
160
  if (
112
161
  !Array.isArray(entry) ||
113
162
  entry.length === 0 ||
114
- !entry.every((item) => typeof item === 'string' && schemes.includes(item))
163
+ !entry.every(item => typeof item === 'string' && schemes.includes(item))
115
164
  ) {
116
165
  invalid(`${path}.${key}`, 'expected a nonempty array of numbering schemes');
117
166
  }
118
- result[key] = [...entry];
167
+
168
+ result[key] = [ ...entry ];
119
169
  }
120
170
  }
171
+
121
172
  return result;
122
173
  }
123
174
 
124
175
  function property(key: string, value: unknown, path: string): unknown {
125
- if (value === undefined) return undefined;
126
- if (key === 'ordered') return orderedMarker(value, path);
127
- if (key === 'checked') return normalizeStyle(value, checkbox, path);
128
- if (key === 'color' || key.endsWith('Color')) return normalizeEditorColor(value, path);
176
+ if (value === undefined) {
177
+ return undefined;
178
+ }
179
+
180
+ if (key === 'ordered') {
181
+ return orderedMarker(value, path);
182
+ }
183
+
184
+ if (key === 'checked') {
185
+ return normalizeStyle(value, checkbox, path);
186
+ }
187
+
188
+ if (key === 'color' || key.endsWith('Color')) {
189
+ return normalizeEditorColor(value, path);
190
+ }
191
+
129
192
  if (key === 'fontFamily') {
130
- if (typeof value !== 'string' || value.length === 0)
193
+ if (typeof value !== 'string' || value.length === 0) {
131
194
  invalid(path, 'expected a nonempty font family');
195
+ }
196
+
132
197
  return value;
133
198
  }
199
+
134
200
  if (enums[key]) {
135
201
  const candidate = key === 'fontWeight' && typeof value === 'number' ? String(value) : value;
136
- if (typeof candidate !== 'string' || !enums[key].includes(candidate))
202
+
203
+ if (typeof candidate !== 'string' || !enums[key].includes(candidate)) {
137
204
  invalid(path, 'unsupported value');
205
+ }
206
+
138
207
  return candidate;
139
208
  }
140
- if (typeof value !== 'number' || !Number.isFinite(value))
209
+
210
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
141
211
  invalid(path, 'expected a finite number');
142
- if (['fontSize', 'lineHeight', 'size', 'scale'].includes(key) && value <= 0)
212
+ }
213
+
214
+ if ([ 'fontSize', 'lineHeight', 'size', 'scale' ].includes(key) && value <= 0) {
143
215
  invalid(path, 'expected a positive number');
144
- if (!key.startsWith('margin') && key !== 'letterSpacing' && value < 0)
216
+ }
217
+
218
+ if (!key.startsWith('margin') && key !== 'letterSpacing' && value < 0) {
145
219
  invalid(path, 'expected a nonnegative number');
220
+ }
221
+
146
222
  return value;
147
223
  }
148
224
 
@@ -153,67 +229,122 @@ function normalizeStyle(
153
229
  ): Record<string, unknown> {
154
230
  const raw: Record<string, unknown> = {};
155
231
  const ancestors = new Set<object>();
232
+
156
233
  function append(entry: unknown, depth: number): void {
157
- if (entry === false || entry == null) return;
158
- if (depth > 64) invalid(path, 'style arrays are nested too deeply');
234
+ if (entry === false || entry == null) {
235
+ return;
236
+ }
237
+
238
+ if (depth > 64) {
239
+ invalid(path, 'style arrays are nested too deeply');
240
+ }
241
+
159
242
  if (Array.isArray(entry)) {
160
- if (ancestors.has(entry)) invalid(path, 'cyclic style array');
243
+ if (ancestors.has(entry)) {
244
+ invalid(path, 'cyclic style array');
245
+ }
246
+
161
247
  ancestors.add(entry);
162
- entry.forEach((item) => append(item, depth + 1));
248
+ entry.forEach(item => append(item, depth + 1));
163
249
  ancestors.delete(entry);
250
+
164
251
  return;
165
252
  }
166
- if (!record(entry)) invalid(path, 'expected a style object or array');
167
- for (const [key, field] of Object.entries(entry)) {
168
- if (!allowed.includes(key)) invalid(`${path}.${key}`, 'unsupported property');
253
+
254
+ if (!record(entry)) {
255
+ invalid(path, 'expected a style object or array');
256
+ }
257
+
258
+ for (const [ key, field ] of Object.entries(entry)) {
259
+ if (!allowed.includes(key)) {
260
+ invalid(`${path}.${key}`, 'unsupported property');
261
+ }
262
+
169
263
  raw[key] = property(key, field, `${path}.${key}`);
170
264
  }
171
265
  }
266
+
172
267
  append(value, 0);
268
+
173
269
  const result = Object.fromEntries(
174
- Object.entries(raw).filter(([, value]) => value !== undefined)
270
+ Object.entries(raw).filter(([ , value ]) => value !== undefined)
175
271
  );
176
- for (const prefix of ['margin', 'padding']) {
272
+
273
+ for (const prefix of [ 'margin', 'padding' ]) {
177
274
  for (const side of sides) {
178
275
  const axis = side === 'Top' || side === 'Bottom' ? 'Vertical' : 'Horizontal';
179
276
  const resolved = raw[`${prefix}${side}`] ?? raw[`${prefix}${axis}`] ?? raw[prefix];
180
- if (resolved !== undefined) result[`${prefix}${side}`] = resolved;
277
+
278
+ if (resolved !== undefined) {
279
+ result[`${prefix}${side}`] = resolved;
280
+ }
181
281
  }
282
+
182
283
  delete result[prefix];
183
284
  delete result[`${prefix}Horizontal`];
184
285
  delete result[`${prefix}Vertical`];
185
286
  }
186
- for (const suffix of ['Width', 'Color']) {
287
+
288
+ for (const suffix of [ 'Width', 'Color' ]) {
187
289
  for (const side of sides) {
188
290
  const resolved = raw[`border${side}${suffix}`] ?? raw[`border${suffix}`];
189
- if (resolved !== undefined) result[`border${side}${suffix}`] = resolved;
291
+
292
+ if (resolved !== undefined) {
293
+ result[`border${side}${suffix}`] = resolved;
294
+ }
190
295
  }
296
+
191
297
  delete result[`border${suffix}`];
192
298
  }
299
+
193
300
  for (const corner of corners) {
194
301
  const resolved = raw[`border${corner}Radius`] ?? raw.borderRadius;
195
- if (resolved !== undefined) result[`border${corner}Radius`] = resolved;
302
+
303
+ if (resolved !== undefined) {
304
+ result[`border${corner}Radius`] = resolved;
305
+ }
196
306
  }
307
+
197
308
  delete result.borderRadius;
309
+
198
310
  return result;
199
311
  }
200
312
 
201
313
  export function normalizeEditorTheme(theme: unknown): NormalizedEditorTheme {
202
- if (!record(theme)) invalid('theme', 'expected a named style map');
314
+ if (!record(theme)) {
315
+ invalid('theme', 'expected a named style map');
316
+ }
317
+
203
318
  const styles: Partial<Record<keyof EditorStyleMap, NormalizedEditorStyle>> = {};
204
319
  const result: NormalizedEditorTheme = { version: 1 };
205
- for (const [key, value] of Object.entries(theme)) {
320
+
321
+ for (const [ key, value ] of Object.entries(theme)) {
206
322
  if (key === 'toolbar') {
207
323
  if (value !== undefined) {
208
- if (!record(value)) invalid('toolbar', 'expected toolbar configuration');
324
+ if (!record(value)) {
325
+ invalid('toolbar', 'expected toolbar configuration');
326
+ }
327
+
209
328
  result.toolbar = { ...value };
210
329
  }
330
+
211
331
  continue;
212
332
  }
213
- if (!Object.prototype.hasOwnProperty.call(fields, key)) invalid(key, 'unsupported element');
333
+
334
+ if (!Object.prototype.hasOwnProperty.call(fields, key)) {
335
+ invalid(key, 'unsupported element');
336
+ }
337
+
214
338
  const style = normalizeStyle(value, fields[key as keyof EditorStyleMap], key);
215
- if (Object.keys(style).length) styles[key as keyof EditorStyleMap] = style;
339
+
340
+ if (Object.keys(style).length) {
341
+ styles[key as keyof EditorStyleMap] = style;
342
+ }
216
343
  }
217
- if (Object.keys(styles).length) result.styles = styles;
344
+
345
+ if (Object.keys(styles).length) {
346
+ result.styles = styles;
347
+ }
348
+
218
349
  return result;
219
350
  }
@@ -150,16 +150,18 @@ export interface EditorContentInsets {
150
150
 
151
151
  function stripUndefined(value: unknown): unknown {
152
152
  if (Array.isArray(value)) {
153
- return value.map((item) => stripUndefined(item)).filter((item) => item !== undefined);
153
+ return value.map(item => stripUndefined(item)).filter(item => item !== undefined);
154
154
  }
155
155
 
156
156
  if (value != null && typeof value === 'object') {
157
157
  const entries = Object.entries(value as Record<string, unknown>)
158
- .map(([key, entryValue]) => [key, stripUndefined(entryValue)] as const)
159
- .filter(([, entryValue]) => entryValue !== undefined);
158
+ .map(([ key, entryValue ]) => [ key, stripUndefined(entryValue) ] as const)
159
+ .filter(([ , entryValue ]) => entryValue !== undefined);
160
+
160
161
  if (entries.length === 0) {
161
162
  return undefined;
162
163
  }
164
+
163
165
  return Object.fromEntries(entries);
164
166
  }
165
167
 
@@ -170,21 +172,26 @@ function stripUndefined(value: unknown): unknown {
170
172
  return value;
171
173
  }
172
174
 
173
- // Mentions are configured on the mentions addon, not on EditorTheme; native
174
- // still reads them from the theme payload.
175
+ /**
176
+ * Mentions are configured on the mentions addon, not on EditorTheme; native
177
+ * still reads them from the theme payload.
178
+ */
175
179
  export function serializeEditorTheme(
176
180
  theme?: EditorTheme,
177
181
  mentionTheme?: EditorMentionTheme
178
182
  ): string | undefined {
179
183
  const normalized = theme === undefined ? undefined : normalizeEditorTheme(theme);
184
+
180
185
  const cleanedTheme =
181
186
  normalized && (normalized.styles || normalized.toolbar)
182
187
  ? stripUndefined(normalized)
183
188
  : undefined;
189
+
184
190
  const base =
185
191
  cleanedTheme && typeof cleanedTheme === 'object'
186
192
  ? (cleanedTheme as Record<string, unknown>)
187
193
  : undefined;
194
+
188
195
  const cleanedMentions = mentionTheme
189
196
  ? stripUndefined(normalizeEditorMentionTheme(mentionTheme))
190
197
  : undefined;
@@ -192,5 +199,6 @@ export function serializeEditorTheme(
192
199
  if (cleanedMentions == null) {
193
200
  return base ? JSON.stringify(base) : undefined;
194
201
  }
202
+
195
203
  return JSON.stringify({ version: 1, ...(base ?? {}), mentions: cleanedMentions });
196
204
  }
@@ -6,7 +6,7 @@ import { useEditorToolbarPresentation } from './useEditorToolbarPresentation';
6
6
 
7
7
  /**
8
8
  * A JavaScript formatting toolbar. `RichTextEditor` renders one for you
9
- * when `showToolbar` is set reach for this component directly only to place
9
+ * when `showToolbar` is set : reach for this component directly only to place
10
10
  * the toolbar somewhere the editor cannot, in which case wire every handler
11
11
  * to the editor's ref and feed it `activeState` and `historyState` from the
12
12
  * editor's callbacks.
@@ -19,5 +19,6 @@ export function EditorToolbar(props: EditorToolbarProps) {
19
19
  const state = useEditorToolbarState(props);
20
20
  const items = useEditorToolbarItems(state);
21
21
  const interactions = useEditorToolbarInteractions({ ...state, ...items });
22
+
22
23
  return useEditorToolbarPresentation({ ...state, ...items, ...interactions });
23
24
  }