Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,13 @@ class EnrichedTextInputViewManager :
view?.requestHTML(requestId)
}

override fun setTextAlignment(
view: EnrichedTextInputView?,
alignment: String,
) {
TODO("Not yet implemented")
}

override fun measure(
context: Context,
localData: ReadableMap?,
Expand Down
27 changes: 27 additions & 0 deletions apps/example/src/components/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ const STYLE_ITEMS = [
name: 'checkbox-list',
icon: 'check-square-o',
},
{
name: 'align-left',
icon: 'align-left',
},
{
name: 'align-center',
icon: 'align-center',
},
{
name: 'align-right',
icon: 'align-right',
},
] as const;

type Item = (typeof STYLE_ITEMS)[number];
Expand Down Expand Up @@ -168,6 +180,15 @@ export const Toolbar: FC<ToolbarProps> = ({
case 'mention':
editorRef.current?.startMention('@');
break;
case 'align-left':
editorRef.current?.setTextAlignment('left');
break;
case 'align-center':
editorRef.current?.setTextAlignment('center');
break;
case 'align-right':
editorRef.current?.setTextAlignment('right');
break;
}
};

Expand Down Expand Up @@ -256,6 +277,12 @@ export const Toolbar: FC<ToolbarProps> = ({
return stylesState.mention.isActive;
case 'checkbox-list':
return stylesState.checkboxList.isActive;
case 'align-left':
return stylesState.alignment === 'left';
case 'align-center':
return stylesState.alignment === 'center';
case 'align-right':
return stylesState.alignment === 'right';
default:
return false;
}
Expand Down
1 change: 1 addition & 0 deletions apps/example/src/constants/editorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const DEFAULT_STYLES: StylesState = {
image: DEFAULT_STYLE_STATE,
mention: DEFAULT_STYLE_STATE,
checkboxList: DEFAULT_STYLE_STATE,
alignment: 'left',
};

export const DEFAULT_LINK_STATE = {
Expand Down
10 changes: 10 additions & 0 deletions apps/example/src/screens/DevScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,14 @@ const styles = StyleSheet.create({
height: 1000,
backgroundColor: 'rgb(0, 26, 114)',
},
alignmentLabel: {
marginTop: 20,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
color: 'rgb(0, 26, 114)',
},
alignmentButton: {
width: '25%',
},
});
12 changes: 12 additions & 0 deletions docs/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,14 @@ interface OnChangeStateEvent {
isConflicting: boolean;
isBlocking: boolean;
};
alignment: string;
}
```

- `isActive` indicates if the style is active within current selection.
- `isBlocking` indicates if the style is blocked by other currently active, meaning it can't be toggled.
- `isConflicting` indicates if the style is in conflict with other currently active styles, meaning toggling it will remove conflicting style.
- `alignment` indicates the current text alignment of the paragraph at the cursor position. Possible values: `'left'`, `'center'`, `'right'`, `'justify'`.

| Type | Platform |
|-------------------------------------------------------------|----------|
Expand Down Expand Up @@ -614,6 +616,16 @@ Sets the selection at the given indexes.
- `start: number` - starting index of the selection.
- `end: number` - first index after the selection's ending index. For just a cursor in place (no selection), `start` equals `end`.

### `.setTextAlignment()`

```ts
setTextAlignment: (alignment: 'left' | 'center' | 'right' | 'justify' | 'default') => void;
```

Sets text alignment for the paragraph(s) at the current selection. When inside a list, the alignment is applied to all contiguous list items.

- `alignment` - the desired text alignment. Use `'default'` to reset to the system natural alignment.

### `.startMention()`

```ts
Expand Down
18 changes: 17 additions & 1 deletion ios/EnrichedTextInputView.mm
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#import "EnrichedTextInputView.h"
#import "AlignmentUtils.h"
#import "CoreText/CoreText.h"
#import "ImageAttachment.h"
#import "KeyboardUtils.h"
#import "LayoutManagerExtension.h"
#import "ParagraphAttributesUtils.h"
#import "ParagraphsUtils.h"
#import "RCTFabricComponentsPlugins.h"
#import "StringExtension.h"
#import "StyleHeaders.h"
Expand Down Expand Up @@ -53,6 +55,7 @@ @implementation EnrichedTextInputView {
NSMutableDictionary<NSValue *, UIImageView *> *_attachmentViews;
NSArray<NSDictionary *> *_contextMenuItems;
NSString *_submitBehavior;
NSString *_recentlyEmittedAlignment;
}

// MARK: - Component utils
Expand Down Expand Up @@ -91,6 +94,7 @@ - (void)setDefaults {
_blockedStyles = [[NSMutableSet alloc] init];
_recentlyActiveLinkRange = NSMakeRange(0, 0);
_recentlyActiveMentionRange = NSMakeRange(0, 0);
_recentlyEmittedAlignment = @"left";
recentlyChangedRange = NSMakeRange(0, 0);
_recentInputString = @"";
_recentlyEmittedHtml = @"<html>\n<p></p>\n</html>";
Expand Down Expand Up @@ -1134,12 +1138,20 @@ - (void)tryUpdatingActiveStyles {
}
}

// detect alignment change
NSString *currentAlignment =
[AlignmentUtils currentAlignmentStringForInput:self];
if (![currentAlignment isEqualToString:_recentlyEmittedAlignment]) {
updateNeeded = YES;
}

if (updateNeeded) {
auto emitter = [self getEventEmitter];
if (emitter != nullptr) {
// update activeStyles and blockedStyles only if emitter is available
_activeStyles = newActiveStyles;
_blockedStyles = newBlockedStyles;
_recentlyEmittedAlignment = currentAlignment;

emitter->onChangeState(
{.bold = GET_STYLE_STATE([BoldStyle getStyleType]),
Expand All @@ -1160,7 +1172,8 @@ - (void)tryUpdatingActiveStyles {
.blockQuote = GET_STYLE_STATE([BlockQuoteStyle getStyleType]),
.codeBlock = GET_STYLE_STATE([CodeBlockStyle getStyleType]),
.image = GET_STYLE_STATE([ImageStyle getStyleType]),
.checkboxList = GET_STYLE_STATE([CheckboxListStyle getStyleType])});
.checkboxList = GET_STYLE_STATE([CheckboxListStyle getStyleType]),
.alignment = [currentAlignment UTF8String]});
}
}

Expand Down Expand Up @@ -1304,6 +1317,9 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
} else if ([commandName isEqualToString:@"requestHTML"]) {
NSInteger requestId = [((NSNumber *)args[0]) integerValue];
[self requestHTML:requestId];
} else if ([commandName isEqualToString:@"setTextAlignment"]) {
NSString *alignmentString = (NSString *)args[0];
[AlignmentUtils applyAlignmentFromString:alignmentString toInput:self];
}
}

Expand Down
25 changes: 16 additions & 9 deletions ios/extensions/LayoutManagerExtension.mm
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
NSForegroundColorAttributeName :
[typedInput->config orderedListMarkerColor]
};
CGFloat indent = pStyle.firstLineHeadIndent;

NSArray *paragraphs = [ParagraphsUtils
getSeparateParagraphsRangesIn:typedInput->textView
Expand Down Expand Up @@ -308,18 +309,21 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
marker:marker
markerAttributes:markerAttributes
origin:origin
usedRect:textUsedRect];
usedRect:textUsedRect
indent:indent];
} else if (markerFormat ==
NSTextListMarkerDisc) {
[self drawBullet:typedInput
origin:origin
usedRect:textUsedRect];
usedRect:textUsedRect
indent:indent];
} else if ([markerFormat
hasPrefix:@"{checkbox"]) {
[self drawCheckbox:typedInput
markerFormat:markerFormat
origin:origin
usedRect:textUsedRect];
usedRect:textUsedRect
indent:indent];
}
// only first line of a list gets its
// marker drawn
Expand Down Expand Up @@ -387,7 +391,8 @@ - (CGRect)getTextAlignedUsedRect:(CGRect)usedRect font:(UIFont *)font {
- (void)drawCheckbox:(EnrichedTextInputView *)typedInput
markerFormat:(NSString *)markerFormat
origin:(CGPoint)origin
usedRect:(CGRect)usedRect {
usedRect:(CGRect)usedRect
indent:(CGFloat)indent {
BOOL isChecked = [markerFormat isEqualToString:@"{checkbox:1}"];

UIImage *image = isChecked ? typedInput->config.checkboxCheckedImage
Expand All @@ -396,18 +401,19 @@ - (void)drawCheckbox:(EnrichedTextInputView *)typedInput
CGFloat boxSize = [typedInput->config checkboxListBoxSize];

CGFloat centerY = CGRectGetMidY(usedRect) + origin.y;
CGFloat boxX = origin.x + usedRect.origin.x - gapWidth - boxSize;
CGFloat boxX = origin.x + indent - gapWidth - boxSize;
CGFloat boxY = centerY - boxSize / 2.0;

[image drawAtPoint:CGPointMake(boxX, boxY)];
}

- (void)drawBullet:(EnrichedTextInputView *)typedInput
origin:(CGPoint)origin
usedRect:(CGRect)usedRect {
usedRect:(CGRect)usedRect
indent:(CGFloat)indent {
CGFloat gapWidth = [typedInput->config unorderedListGapWidth];
CGFloat bulletSize = [typedInput->config unorderedListBulletSize];
CGFloat bulletX = origin.x + usedRect.origin.x - gapWidth - bulletSize / 2;
CGFloat bulletX = origin.x + indent - gapWidth - bulletSize / 2;
CGFloat centerY = CGRectGetMidY(usedRect) + origin.y;

CGContextRef context = UIGraphicsGetCurrentContext();
Expand All @@ -425,10 +431,11 @@ - (void)drawDecimal:(EnrichedTextInputView *)typedInput
marker:(NSString *)marker
markerAttributes:(NSDictionary *)markerAttributes
origin:(CGPoint)origin
usedRect:(CGRect)usedRect {
usedRect:(CGRect)usedRect
indent:(CGFloat)indent {
CGFloat gapWidth = [typedInput->config orderedListGapWidth];
CGSize markerSize = [marker sizeWithAttributes:markerAttributes];
CGFloat markerX = usedRect.origin.x - gapWidth - markerSize.width / 2;
CGFloat markerX = origin.x + indent - gapWidth - markerSize.width / 2;
CGFloat centerY = CGRectGetMidY(usedRect) + origin.y;
CGFloat markerY = centerY - markerSize.height / 2.0;

Expand Down
Loading
Loading