@apollohg/react-native-rich-text-editor 2.0.4 → 2.0.6

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 (47) hide show
  1. package/README.md +11 -0
  2. package/android/src/main/java/com/apollohg/editor/EditorCheckboxSpan.kt +8 -3
  3. package/android/src/main/java/com/apollohg/editor/EditorEditTextConfiguration.kt +5 -1
  4. package/android/src/main/java/com/apollohg/editor/EditorEditTextToolbar.kt +1 -1
  5. package/android/src/main/java/com/apollohg/editor/EditorMentionStyle.kt +3 -2
  6. package/android/src/main/java/com/apollohg/editor/EditorStyleSheet.kt +196 -8
  7. package/android/src/main/java/com/apollohg/editor/EditorV2Adapter.kt +7 -0
  8. package/android/src/main/java/com/apollohg/editor/EditorV2Driver.kt +1 -0
  9. package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +48 -9
  10. package/android/src/main/java/com/apollohg/editor/RenderBridgeBlockStyles.kt +17 -4
  11. package/android/src/main/java/com/apollohg/editor/RenderBridgeElements.kt +83 -25
  12. package/android/src/main/java/com/apollohg/editor/RenderBridgeInlineAndAtoms.kt +28 -14
  13. package/android/src/main/java/com/apollohg/editor/RenderParagraphSpans.kt +5 -1
  14. package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +5 -1
  15. package/android/src/main/java/com/apollohg/editor/viewer/AndroidProseLayoutEngine.kt +141 -40
  16. package/dist/EditorStyleSheetNormalization.js +37 -0
  17. package/dist/EditorStyleSheetTypes.d.ts +22 -1
  18. package/dist/EditorTheme.js +4 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/useRichTextEditorCommands.js +1 -6
  21. package/ios/EditorCore.xcframework/Info.plist +5 -5
  22. package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +2 -2
  23. package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +2 -2
  24. package/ios/EditorLayoutManager.swift +2 -4
  25. package/ios/EditorStyleSheet.swift +77 -11
  26. package/ios/EditorTextView+Commands.swift +1 -1
  27. package/ios/EditorTextView.swift +5 -2
  28. package/ios/EditorTheme.swift +3 -1
  29. package/ios/EditorV2Adapter+Commands.swift +10 -0
  30. package/ios/EditorV2Shadow.swift +4 -0
  31. package/ios/RenderBridge+Atoms.swift +15 -6
  32. package/ios/RenderBridge+Style.swift +59 -17
  33. package/ios/RenderBridge.swift +19 -16
  34. package/ios/Viewer/CoreTextProseLayoutEngine+AttributedText.swift +31 -13
  35. package/ios/Viewer/CoreTextProseLayoutEngine+Models.swift +17 -2
  36. package/ios/Viewer/CoreTextProseLayoutEngine.swift +39 -21
  37. package/ios/Viewer/Fabric/PREPPreparedProseViewerComponentView.mm +1 -0
  38. package/package.json +3 -3
  39. package/rust/android/arm64-v8a/libeditor_core.so +0 -0
  40. package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
  41. package/rust/android/x86/libeditor_core.so +0 -0
  42. package/rust/android/x86_64/libeditor_core.so +0 -0
  43. package/src/EditorStyleSheetNormalization.ts +50 -0
  44. package/src/EditorStyleSheetTypes.ts +51 -6
  45. package/src/EditorTheme.ts +5 -1
  46. package/src/index.ts +1 -0
  47. package/src/useRichTextEditorCommands.tsx +1 -7
@@ -90,7 +90,9 @@ private data class PreparedMarker(
90
90
  val ascentPx: Int,
91
91
  val baselinePx: Int,
92
92
  val checked: Boolean,
93
- val checkbox: com.apollohg.editor.EditorElementStyle? = null
93
+ val checkbox: com.apollohg.editor.EditorElementStyle? = null,
94
+ val color: Int? = null,
95
+ val gapPx: Int? = null
94
96
  )
95
97
 
96
98
  internal const val PREPARED_LIST_MARKER_GAP_DP = 6f
@@ -251,7 +253,8 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
251
253
  ancestor.context,
252
254
  nestingDepth,
253
255
  theme.paintFor(block),
254
- theme
256
+ theme,
257
+ markerAncestors(block, nestingDepth)
255
258
  )
256
259
  }
257
260
  }
@@ -259,7 +262,9 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
259
262
  val containerBounds = mutableMapOf<Int, Rect>()
260
263
  val sheet = theme.sourceTheme?.styleSheet
261
264
  val leafBoxes = visibleBlocks.mapIndexed { index, block ->
262
- val box = sheet?.box(block.nodeType)?.scaled(density) ?: EditorBoxStyle()
265
+ val box =
266
+ sheet?.box(block.nodeType, block.containers.map { it.nodeType })?.scaled(density)
267
+ ?: EditorBoxStyle()
263
268
  val parent = block.containers.lastOrNull()
264
269
  if (block.nodeType == "paragraph" && parent?.nodeType == "blockquote" &&
265
270
  parent.lastLeaf == index
@@ -288,13 +293,23 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
288
293
  }
289
294
  val bottom = (
290
295
  previous.getOrNull(common)?.let {
291
- sheet.box(it.nodeType).scaled(density).margin.bottom
296
+ sheet.box(
297
+ it.nodeType,
298
+ previous.take(common).map {
299
+ it.nodeType
300
+ }
301
+ ).scaled(density).margin.bottom
292
302
  }
293
303
  ?: leafBoxes[index - 1].margin.bottom
294
304
  ).toInt()
295
305
  val top = (
296
306
  containers.getOrNull(common)?.let {
297
- sheet.box(it.nodeType).scaled(density).margin.top
307
+ sheet.box(
308
+ it.nodeType,
309
+ containers.take(common).map {
310
+ it.nodeType
311
+ }
312
+ ).scaled(density).margin.top
298
313
  }
299
314
  ?: leafBoxes[index].margin.top
300
315
  ).toInt()
@@ -302,7 +317,13 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
302
317
  }
303
318
  var containerInset = EditorEdges()
304
319
  containers.forEach { ancestor ->
305
- val box = sheet!!.box(ancestor.nodeType).scaled(density)
320
+ val box = sheet!!.box(
321
+ ancestor.nodeType,
322
+ containers.takeWhile {
323
+ it.identity !=
324
+ ancestor.identity
325
+ }.map { it.nodeType }
326
+ ).scaled(density)
306
327
  if (ancestor.firstLeaf == index) {
307
328
  cursorY += box.margin.top.toInt()
308
329
  containerBounds[ancestor.identity] =
@@ -379,7 +400,13 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
379
400
  cursorY = prepared.nextY
380
401
  containers.asReversed().forEach { ancestor ->
381
402
  if (ancestor.lastLeaf == index) {
382
- val box = sheet!!.box(ancestor.nodeType).scaled(density)
403
+ val box = sheet!!.box(
404
+ ancestor.nodeType,
405
+ containers.takeWhile {
406
+ it.identity !=
407
+ ancestor.identity
408
+ }.map { it.nodeType }
409
+ ).scaled(density)
383
410
  cursorY += box.inset.bottom.toInt()
384
411
  containerBounds[ancestor.identity]?.bottom = cursorY
385
412
  cursorY += box.margin.bottom.toInt()
@@ -420,7 +447,13 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
420
447
  PreparedProseFragment(
421
448
  PreparedProseFragmentKind.BACKGROUND,
422
449
  clip,
423
- box = sheet.box(ancestor.nodeType).scaled(density),
450
+ box = sheet.box(
451
+ ancestor.nodeType,
452
+ visibleBlocks[index].containers.takeWhile {
453
+ it.identity !=
454
+ ancestor.identity
455
+ }.map { it.nodeType }
456
+ ).scaled(density),
424
457
  decorationBounds = bounds
425
458
  )
426
459
  }
@@ -545,17 +578,19 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
545
578
  val firstMarkers = ancestorMarkers.filter { (ancestor, _) ->
546
579
  ancestor.isFirstRenderableLeaf
547
580
  }
548
- fun listStyle(ancestor: ViewerListItemAncestor) = theme.sourceTheme?.styleSheet?.get(
549
- if (ancestor.context.kind ==
550
- "task"
551
- ) {
552
- "taskList"
553
- } else if (ancestor.context.ordered) {
554
- "orderedList"
555
- } else {
556
- "bulletList"
557
- }
558
- )
581
+ fun listStyle(ancestor: ViewerListItemAncestor) =
582
+ theme.sourceTheme?.styleSheet?.resolveElement(
583
+ if (ancestor.context.kind ==
584
+ "task"
585
+ ) {
586
+ "taskList"
587
+ } else if (ancestor.context.ordered) {
588
+ "orderedList"
589
+ } else {
590
+ "bulletList"
591
+ },
592
+ markerAncestors(block, ancestors.indexOf(ancestor)).dropLast(2)
593
+ )
559
594
  fun listIndent(ancestor: ViewerListItemAncestor) =
560
595
  listStyle(ancestor)?.indent?.times(theme.density)?.toInt() ?: theme.listIndentPx
561
596
  val baseListInset = if (ancestors.isEmpty()) {
@@ -578,7 +613,7 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
578
613
  marker.widthPx +
579
614
  (
580
615
  marker.checkbox?.gap?.times(theme.density)?.toInt()
581
- ?: theme.listMarkerGapPx
616
+ ?: marker.gapPx ?: theme.listMarkerGapPx
582
617
  )
583
618
  )
584
619
  }
@@ -722,10 +757,16 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
722
757
  PreparedProseFragmentKind.IMAGE,
723
758
  bounds,
724
759
  color =
725
- theme.sourceTheme?.styleSheet?.box("image")?.backgroundColor
760
+ theme.sourceTheme?.styleSheet?.box(
761
+ "image",
762
+ block.containers.map {
763
+ it.nodeType
764
+ }
765
+ )?.backgroundColor
726
766
  ?: 0xFFF2F2F7.toInt(),
727
767
  box = theme.sourceTheme?.styleSheet?.box(
728
- "image"
768
+ "image",
769
+ block.containers.map { it.nodeType }
729
770
  )?.scaled(theme.density)?.let {
730
771
  it.copy(
731
772
  border = EditorEdges(),
@@ -736,7 +777,12 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
736
777
  )
737
778
  },
738
779
  resizeMode =
739
- theme.sourceTheme?.styleSheet?.get("image")?.resizeMode
780
+ theme.sourceTheme?.styleSheet?.resolveElement(
781
+ "image",
782
+ block.containers.map {
783
+ it.nodeType
784
+ }
785
+ )?.resizeMode
740
786
  ?: "contain"
741
787
  )
742
788
  ),
@@ -750,20 +796,32 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
750
796
  }
751
797
  }
752
798
  if (block.nodeType == "horizontalRule" || block.nodeType == "horizontal_rule") {
799
+ val sheet = theme.sourceTheme?.styleSheet
800
+ val ancestors = block.containers.map { it.nodeType }
801
+ val ruleStyle = sheet?.resolveElement("horizontalRule", ancestors)
802
+ val thickness = if (
803
+ sheet?.hasMatchingRuleProperty("horizontalRule", ancestors, "height") == true
804
+ ) {
805
+ ruleStyle?.height?.times(theme.density)?.toInt() ?: theme.ruleThicknessPx
806
+ } else {
807
+ theme.ruleThicknessPx
808
+ }
753
809
  val ruleTop = cursorY + theme.ruleMarginPx
754
810
  val ruleLeft = theme.insetLeftPx + listInset + quoteInset
755
811
  val ruleRight = max(
756
812
  ruleLeft + 1,
757
813
  theme.insetLeftPx + contentWidth - listInset - quoteInset
758
814
  )
759
- val rule = Rect(ruleLeft, ruleTop, ruleRight, ruleTop + theme.ruleThicknessPx)
815
+ val rule = Rect(ruleLeft, ruleTop, ruleRight, ruleTop + thickness)
760
816
  val fragments =
761
817
  mutableListOf(
762
818
  PreparedProseFragment(
763
819
  PreparedProseFragmentKind.RULE,
764
820
  rule,
765
- color = theme.ruleColor,
766
- strokeWidth = theme.ruleThicknessPx.toFloat()
821
+ color =
822
+ ruleStyle?.box?.backgroundColor ?: ruleStyle?.text?.color
823
+ ?: theme.ruleColor,
824
+ strokeWidth = thickness.toFloat()
767
825
  )
768
826
  )
769
827
  val end = rule.bottom + theme.ruleMarginPx
@@ -808,7 +866,13 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
808
866
  }
809
867
 
810
868
  val availableWidth = max(1, contentWidth - listInset - quoteInset - codeInset * 2)
811
- val attributed = attributed(block.inlines, paint, theme, warningSemanticGeneration)
869
+ val attributed = attributed(
870
+ block.inlines,
871
+ paint,
872
+ theme,
873
+ warningSemanticGeneration,
874
+ block.containers.map { it.nodeType } + block.nodeType
875
+ )
812
876
  var highlightedCodeKey: String? = null
813
877
  if (block.nodeType == "codeBlock") {
814
878
  theme.codeHighlighting?.let { config ->
@@ -1126,7 +1190,8 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1126
1190
  inlines: List<ViewerInline>,
1127
1191
  base: PreparedTextPaint,
1128
1192
  theme: PreparedProseTheme,
1129
- warningSemanticGeneration: String
1193
+ warningSemanticGeneration: String,
1194
+ ancestors: List<String>
1130
1195
  ): AttributedBlock {
1131
1196
  val source = StringBuilder()
1132
1197
  val spans = mutableListOf<(SpannableString) -> Unit>()
@@ -1138,7 +1203,8 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1138
1203
  val start = source.length
1139
1204
  source.append(inline.text)
1140
1205
  val end = source.length
1141
- val markSpans = markSpans(inline.marks, base, theme, warningSemanticGeneration)
1206
+ val markSpans =
1207
+ markSpans(inline.marks, base, theme, warningSemanticGeneration, ancestors)
1142
1208
  spans +=
1143
1209
  { value ->
1144
1210
  markSpans.forEach {
@@ -1169,7 +1235,13 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1169
1235
  source.append('\n')
1170
1236
  } else {
1171
1237
  val appearance =
1172
- atomAppearance(inline.nodeType, inline.attrsJson, base, theme)
1238
+ atomAppearance(
1239
+ inline.nodeType,
1240
+ inline.attrsJson,
1241
+ base,
1242
+ theme,
1243
+ ancestors
1244
+ )
1173
1245
  val label = inline.label.ifEmpty { " " }
1174
1246
  val labelPaint = appearance.paint.newTextPaint()
1175
1247
  val atomInset =
@@ -1292,7 +1364,8 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1292
1364
  marks: List<uniffi.editor_core.FfiViewerMark>,
1293
1365
  base: PreparedTextPaint,
1294
1366
  theme: PreparedProseTheme,
1295
- warningSemanticGeneration: String
1367
+ warningSemanticGeneration: String,
1368
+ ancestors: List<String>
1296
1369
  ): List<Any> {
1297
1370
  theme.sourceTheme?.styleSheet?.let { sheet ->
1298
1371
  var resolved = base.resolvedStyle ?: EditorTextStyle()
@@ -1304,7 +1377,7 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1304
1377
  resolved =
1305
1378
  resolved.mergedWith(
1306
1379
  com.apollohg.editor.semanticText(it)
1307
- ).mergedWith(sheet[it]?.text)
1380
+ ).mergedWith(sheet.resolveElement(it, ancestors)?.text)
1308
1381
  }
1309
1382
  marks.forEach { mark ->
1310
1383
  val attrs = runCatching { org.json.JSONObject(mark.attrsJson) }.getOrNull()
@@ -1491,16 +1564,34 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1491
1564
  .build()
1492
1565
  }
1493
1566
 
1567
+ private fun markerAncestors(block: ViewerBlock, nestingDepth: Int): List<String> {
1568
+ val chain = block.containers.map { it.nodeType }
1569
+ val item = chain.indices.filter {
1570
+ com.apollohg.editor.canonicalElement(chain[it]) in
1571
+ listOf("listItem", "taskItem")
1572
+ }.getOrNull(nestingDepth)
1573
+ return item?.let { chain.take(it + 1) } ?: chain
1574
+ }
1575
+
1494
1576
  private fun markerFor(
1495
1577
  context: ViewerListContext,
1496
1578
  nestingDepth: Int,
1497
1579
  textPaint: PreparedTextPaint,
1498
- theme: PreparedProseTheme
1580
+ theme: PreparedProseTheme,
1581
+ ancestors: List<String>
1499
1582
  ): PreparedMarker {
1583
+ val style = theme.sourceTheme?.styleSheet?.resolveElement("listMarker", ancestors)
1584
+ val theme = theme.copy(
1585
+ listMarkerColor = style?.text?.color ?: theme.listMarkerColor,
1586
+ listMarkerScale = style?.scale ?: theme.listMarkerScale,
1587
+ orderedListMarker = style?.ordered ?: theme.orderedListMarker
1588
+ )
1589
+ val gap = style?.gap?.times(theme.density)?.toInt()
1500
1590
  if (context.kind == "task" && theme.sourceTheme?.styleSheet != null) {
1501
1591
  val checkbox = com.apollohg.editor.resolvedCheckboxStyle(
1502
1592
  theme.sourceTheme.styleSheet,
1503
- context.checked
1593
+ context.checked,
1594
+ ancestors
1504
1595
  )
1505
1596
  val side = ((checkbox.size ?: 18f) * theme.density).toInt()
1506
1597
  return PreparedMarker(
@@ -1511,7 +1602,9 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1511
1602
  side / 2,
1512
1603
  0,
1513
1604
  context.checked,
1514
- checkbox.copy(box = checkbox.box.scaled(theme.density))
1605
+ checkbox.copy(box = checkbox.box.scaled(theme.density)),
1606
+ theme.listMarkerColor,
1607
+ gap
1515
1608
  )
1516
1609
  }
1517
1610
  val label = when {
@@ -1542,7 +1635,11 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1542
1635
  markerPaint.newTextPaint().fontMetricsInt.descent -
1543
1636
  markerPaint.newTextPaint().fontMetricsInt.ascent
1544
1637
  )
1545
- return PreparedMarker(null, label, side, side, side / 2, 0, context.checked)
1638
+ return PreparedMarker(
1639
+ null, label, side, side, side / 2, 0, context.checked,
1640
+ color = theme.listMarkerColor,
1641
+ gapPx = gap
1642
+ )
1546
1643
  }
1547
1644
  val text = markerPaint.newTextPaint()
1548
1645
  val layout =
@@ -1561,7 +1658,9 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1561
1658
  ink.heightPx,
1562
1659
  ink.ascentPx,
1563
1660
  layout.getLineBaseline(0),
1564
- context.checked
1661
+ context.checked,
1662
+ color = theme.listMarkerColor,
1663
+ gapPx = gap
1565
1664
  )
1566
1665
  }
1567
1666
 
@@ -1587,7 +1686,7 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1587
1686
  marker.layout,
1588
1687
  x,
1589
1688
  layoutY,
1590
- color = color,
1689
+ color = marker.color ?: color,
1591
1690
  label = marker.label,
1592
1691
  checked = marker.checked,
1593
1692
  box = marker.checkbox?.box,
@@ -1599,7 +1698,8 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1599
1698
  nodeType: String,
1600
1699
  attrsJson: String,
1601
1700
  base: PreparedTextPaint,
1602
- theme: PreparedProseTheme
1701
+ theme: PreparedProseTheme,
1702
+ ancestors: List<String>
1603
1703
  ): PreparedAtomAppearance {
1604
1704
  if (nodeType == "mention") {
1605
1705
  val values = runCatching { org.json.JSONObject(attrsJson) }.getOrNull()
@@ -1608,7 +1708,8 @@ internal class StaticLayoutAndroidProseLayoutEngine : AndroidProseLayoutEngine {
1608
1708
  val element = com.apollohg.editor.resolvedMentionStyle(
1609
1709
  base.resolvedStyle ?: EditorTextStyle(),
1610
1710
  theme.sourceTheme,
1611
- local
1711
+ local,
1712
+ ancestors
1612
1713
  )
1613
1714
  val box = element.box.scaled(theme.density)
1614
1715
  return PreparedAtomAppearance(
@@ -249,6 +249,43 @@ function normalizeEditorTheme(theme) {
249
249
  const styles = {};
250
250
  const result = { version: 1 };
251
251
  for (const [key, value] of Object.entries(theme)) {
252
+ if (key === 'rules') {
253
+ if (value === undefined) {
254
+ continue;
255
+ }
256
+ if (!Array.isArray(value)) {
257
+ invalid('rules', 'expected an array');
258
+ }
259
+ result.rules = value.map((entry, index) => {
260
+ const indexedPath = `rules[${index}]`;
261
+ if (!record(entry)) {
262
+ invalid(indexedPath, 'expected a rule object');
263
+ }
264
+ for (const propertyName of Object.keys(entry)) {
265
+ if (propertyName !== 'path' && propertyName !== 'style') {
266
+ invalid(`${indexedPath}.${propertyName}`, 'unsupported property');
267
+ }
268
+ }
269
+ if (!Array.isArray(entry.path) || entry.path.length === 0) {
270
+ invalid(`${indexedPath}.path`, 'expected a nonempty array');
271
+ }
272
+ entry.path.forEach((element, pathIndex) => {
273
+ if (typeof element !== 'string' ||
274
+ !Object.prototype.hasOwnProperty.call(fields, element)) {
275
+ invalid(`${indexedPath}.path[${pathIndex}]`, 'unsupported element');
276
+ }
277
+ });
278
+ if (!Object.prototype.hasOwnProperty.call(entry, 'style')) {
279
+ invalid(`${indexedPath}.style`, 'expected an own style property');
280
+ }
281
+ const target = entry.path[entry.path.length - 1];
282
+ return {
283
+ path: [...entry.path],
284
+ style: normalizeStyle(entry.style, fields[target], `${indexedPath}.style`),
285
+ };
286
+ });
287
+ continue;
288
+ }
252
289
  if (key === 'toolbar') {
253
290
  if (value !== undefined) {
254
291
  if (!record(value)) {
@@ -123,9 +123,16 @@ export interface EditorStyleMap {
123
123
  mention: EditorMentionStyle;
124
124
  placeholder: EditorTypographyStyle;
125
125
  }
126
+ export type EditorStyleRule = {
127
+ [K in keyof EditorStyleMap]: {
128
+ path: readonly [...(keyof EditorStyleMap)[], K];
129
+ style: EditorStyleProp<EditorStyleMap[K]>;
130
+ };
131
+ }[keyof EditorStyleMap];
126
132
  export type EditorTheme = {
127
133
  [K in keyof EditorStyleMap]?: EditorStyleProp<EditorStyleMap[K]>;
128
134
  } & {
135
+ rules?: readonly EditorStyleRule[];
129
136
  toolbar?: EditorToolbarTheme;
130
137
  };
131
138
  export type EditorLinkTheme = EditorInlineStyle;
@@ -138,6 +145,10 @@ export type NormalizedEditorStyle = Readonly<Record<string, unknown>>;
138
145
  export interface NormalizedEditorTheme {
139
146
  version: 1;
140
147
  styles?: Partial<Record<keyof EditorStyleMap, NormalizedEditorStyle>>;
148
+ rules?: readonly {
149
+ path: readonly (keyof EditorStyleMap)[];
150
+ style: NormalizedEditorStyle;
151
+ }[];
141
152
  toolbar?: EditorToolbarTheme;
142
153
  }
143
154
  type ExactStyle<T, Shape> = T extends false | null | undefined ? T : T extends readonly unknown[] ? {
@@ -145,7 +156,17 @@ type ExactStyle<T, Shape> = T extends false | null | undefined ? T : T extends r
145
156
  } : Shape & {
146
157
  [K in keyof T]: K extends keyof Shape ? K extends 'checked' ? ExactStyle<T[K], EditorCheckboxAppearance> : K extends 'ordered' ? ExactStyle<T[K], EditorOrderedListMarkerTheme> : Shape[K] : never;
147
158
  };
159
+ type ExactRuleStyle<T, Shape> = [EditorStyleProp<Shape>] extends [T] ? [T] extends [EditorStyleProp<Shape>] ? T : ExactStyle<T, Shape> : ExactStyle<T, Shape>;
160
+ type ExactEditorStyleRule<T> = T extends {
161
+ readonly path: infer Path extends readonly (keyof EditorStyleMap)[];
162
+ readonly style: unknown;
163
+ } ? Path extends readonly [...(keyof EditorStyleMap)[], infer Target] ? Target extends keyof EditorStyleMap ? {
164
+ [K in keyof T]: K extends 'path' ? Path : K extends 'style' ? ExactRuleStyle<T[K], EditorStyleMap[Target]> : never;
165
+ } : never : never : never;
166
+ type ExactEditorStyleRules<T> = T extends undefined ? T : T extends readonly unknown[] ? {
167
+ [K in keyof T]: ExactEditorStyleRule<T[K]>;
168
+ } : never;
148
169
  export type ExactEditorTheme<T> = {
149
- [K in keyof T]: K extends keyof EditorStyleMap ? ExactStyle<T[K], EditorStyleMap[K]> : K extends 'toolbar' ? EditorToolbarTheme : never;
170
+ [K in keyof T]: K extends 'rules' ? ExactEditorStyleRules<T[K]> : K extends keyof EditorStyleMap ? ExactStyle<T[K], EditorStyleMap[K]> : K extends 'toolbar' ? EditorToolbarTheme : never;
150
171
  };
151
172
  export {};
@@ -27,12 +27,15 @@ function stripUndefined(value) {
27
27
  */
28
28
  function serializeEditorTheme(theme, mentionTheme) {
29
29
  const normalized = theme === undefined ? undefined : (0, EditorStyleSheetNormalization_1.normalizeEditorTheme)(theme);
30
- const cleanedTheme = normalized && (normalized.styles || normalized.toolbar)
30
+ const cleanedTheme = normalized && (normalized.styles || normalized.rules || normalized.toolbar)
31
31
  ? stripUndefined(normalized)
32
32
  : undefined;
33
33
  const base = cleanedTheme && typeof cleanedTheme === 'object'
34
34
  ? cleanedTheme
35
35
  : undefined;
36
+ if (base && normalized?.rules !== undefined) {
37
+ base.rules = normalized.rules;
38
+ }
36
39
  const cleanedMentions = mentionTheme
37
40
  ? stripUndefined((0, EditorMentionThemeNormalization_1.normalizeEditorMentionTheme)(mentionTheme))
38
41
  : undefined;
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { EditorToolbar, DEFAULT_EDITOR_TOOLBAR_ITEMS, type EditorToolbarProps, t
5
5
  export type { EditorContentInsets, EditorTheme, EditorTextStyle, EditorLinkTheme, EditorHeadingTheme, EditorListTheme, EditorOrderedListNumberingScheme, EditorOrderedListMarkerTheme, EditorHorizontalRuleTheme, EditorBlockquoteTheme, EditorCodeBlockTheme, EditorMentionTheme, EditorMentionNodeTheme, EditorMentionSuggestionsTheme, EditorMentionSuggestionOptionTheme, EditorToolbarTheme, EditorToolbarAppearance, EditorFontStyle, EditorFontWeight, } from './EditorTheme';
6
6
  export { serializeEditorTheme } from './EditorTheme';
7
7
  export { EditorStyleSheet } from './EditorStyleSheet';
8
- export type { EditorStyleProp, EditorStyleMap, EditorTypographyStyle, EditorBorderStyle, EditorPaddingStyle, EditorMarginStyle, EditorSurfaceStyle, EditorBoxStyle, EditorInlineStyle, EditorImageStyle, EditorListStyle, EditorListMarkerStyle, EditorCheckboxAppearance, EditorTaskCheckboxStyle, EditorHorizontalRuleStyle, EditorMentionStyle, } from './EditorStyleSheetTypes';
8
+ export type { EditorStyleProp, EditorStyleRule, EditorStyleMap, EditorTypographyStyle, EditorBorderStyle, EditorPaddingStyle, EditorMarginStyle, EditorSurfaceStyle, EditorBoxStyle, EditorInlineStyle, EditorImageStyle, EditorListStyle, EditorListMarkerStyle, EditorCheckboxAppearance, EditorTaskCheckboxStyle, EditorHorizontalRuleStyle, EditorMentionStyle, } from './EditorStyleSheetTypes';
9
9
  export { createMentionsAddon, type EditorAddon, type EditorAddonEntry, type MentionsAddon, type MentionsAddonOptions, type MentionPressEvent, type CodeHighlightingAddon, type CodeHighlightingAddonOptions, } from './EditorAddon';
10
10
  export { MENTION_NODE_NAME, mentionNodeSpec, withMentionsSchema, buildMentionFragmentJson, type EditorAddons, type MentionsAddonConfig, type MentionSuggestion, type MentionQueryChangeEvent, type MentionFragmentOptions, type MentionSelectionAttrsEvent, type MentionThemeResolveEvent, type MentionSelectEvent, type EditorAddonEvent, } from './addons';
11
11
  export { defineAtomNode, withAtomsSchema, serializeEditorAtoms, type AtomAttrsUpdate, type AtomEditorActions, type InferAtomAttrs, type AtomComponent, type AtomComponentProps, type AtomNodeConfig, type AtomNodeDefinition, type SerializedEditorAtoms, } from './atoms';
@@ -190,13 +190,8 @@ function useRichTextEditorCommands(context) {
190
190
  return;
191
191
  }
192
192
  applyEngineCommand({
193
- type: 'wrapInList',
193
+ type: 'applyListType',
194
194
  listType,
195
- itemType: listType === 'taskList'
196
- ? 'taskItem'
197
- : listType === 'bullet_list' || listType === 'ordered_list'
198
- ? 'list_item'
199
- : 'listItem',
200
195
  });
201
196
  }, [activeStateRef, applyEngineCommand]);
202
197
  const commandIndentListItem = (0, react_1.useCallback)(() => applyEngineCommand({ type: 'indentListItem' }), [applyEngineCommand]);
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>libeditor_core.a</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64</string>
11
+ <string>ios-arm64_x86_64-simulator</string>
12
12
  <key>LibraryPath</key>
13
13
  <string>libeditor_core.a</string>
14
14
  <key>SupportedArchitectures</key>
15
15
  <array>
16
16
  <string>arm64</string>
17
+ <string>x86_64</string>
17
18
  </array>
18
19
  <key>SupportedPlatform</key>
19
20
  <string>ios</string>
21
+ <key>SupportedPlatformVariant</key>
22
+ <string>simulator</string>
20
23
  </dict>
21
24
  <dict>
22
25
  <key>BinaryPath</key>
23
26
  <string>libeditor_core.a</string>
24
27
  <key>LibraryIdentifier</key>
25
- <string>ios-arm64_x86_64-simulator</string>
28
+ <string>ios-arm64</string>
26
29
  <key>LibraryPath</key>
27
30
  <string>libeditor_core.a</string>
28
31
  <key>SupportedArchitectures</key>
29
32
  <array>
30
33
  <string>arm64</string>
31
- <string>x86_64</string>
32
34
  </array>
33
35
  <key>SupportedPlatform</key>
34
36
  <string>ios</string>
35
- <key>SupportedPlatformVariant</key>
36
- <string>simulator</string>
37
37
  </dict>
38
38
  </array>
39
39
  <key>CFBundlePackageType</key>
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a
3
- size: 33938416 bytes
4
- sha256: 9701cff9f1597268a85581ed491f12785d9084efc30cca999c26d33fda3eea42
3
+ size: 33937224 bytes
4
+ sha256: 932bff445a503ec5a28a4d75e1273280144e7112315aa22799a871730bc655f2
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a
3
- size: 67218432 bytes
4
- sha256: d4c1b1e920a953feb892fbd621009162112b4a1786d7ed8e58a0bc8228f3a265
3
+ size: 67220840 bytes
4
+ sha256: 623edc61d1273935a1d1afc314a13fd89f7cce5724ae28f96adc6d8df0b0f718
@@ -503,7 +503,7 @@ final class EditorLayoutManager: NSLayoutManager, NSLayoutManagerDelegate {
503
503
 
504
504
  if ordered {
505
505
  let markerFont = attrs[editorStyledContentAttribute] != nil
506
- ? baseFont.withSize(baseFont.pointSize * markerScale)
506
+ ? baseFont
507
507
  : markerFont(for: listContext, baseFont: baseFont, markerScale: markerScale)
508
508
  let markerText = attrs[RenderBridgeAttributes.orderedListMarkerLabel] as? String
509
509
  ?? RenderBridge.listMarkerString(listContext: listContext)
@@ -878,9 +878,7 @@ final class EditorLayoutManager: NSLayoutManager, NSLayoutManagerDelegate {
878
878
  origin: CGPoint,
879
879
  markerGap: CGFloat = LayoutConstants.listMarkerTextGap
880
880
  ) -> CGRect {
881
- let markerFont = baseFont.withSize(baseFont.pointSize * markerScale)
882
- let bulletBounds = unorderedBulletGlyphBounds(for: markerFont)
883
- let bulletDiameter = max(max(bulletBounds.width, bulletBounds.height), 1)
881
+ let bulletDiameter = max(baseFont.pointSize * markerScale * 0.32, 4)
884
882
  let targetCenterAboveBaseline = (baseFont.xHeight > 0 ? baseFont.xHeight : baseFont.capHeight) / 2.0
885
883
  let centerY = baselineY - targetCenterAboveBaseline
886
884
  let referenceRect = usedRect.height > 0 ? usedRect : lineFragmentRect