Skip to content

feat: custom style - #817

Open
hejsztynx wants to merge 12 commits into
mainfrom
@kacperzolkiewski/custom-style
Open

hejsztynx wants to merge 12 commits into
mainfrom
@kacperzolkiewski/custom-style

Conversation

@hejsztynx

Copy link
Copy Markdown
Collaborator

Summary

Implements the custom style functionality. It introduces backgroundColor, foregroundColor, fontSize and fontFamily formats. For more info, visit relevant sub-PRs

Compatibility

OS Implemented
iOS ✅
Android ✅
Web ✅

Checklist

  • E2E tests are passing
  • Required E2E tests have been added (if applicable)

kacperzolkiewski and others added 11 commits October 12, 2026 11:56
# Summary

Add custom style for foreground and background colors handling

## Test Plan
Run the example app and experiment with applying foreground and
background colors through the toolbar across different text styles.

## Screenshots / Videos


https://github.com/user-attachments/assets/b922cb42-4911-4285-9385-4c4f6a6d7b9c


## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |    ✅     |
| Android |    ❌     |

## Checklist

- [x] E2E tests are passing
- [x] Required E2E tests have been added (if applicable)
# Summary

Add custom style to support foreground and background colors

## Test Plan
Run the example app and experiment with applying foreground and
background colors through the toolbar across different text styles.

## Screenshots / Videos


https://github.com/user-attachments/assets/d3ae479f-b274-415a-9bec-a0fc39e7d9cd



## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |    ❌     |
| Android |    ✅     |

## Checklist

- [x] E2E tests are passing
- [X] Required E2E tests have been added (if applicable)

---------

Co-authored-by: Krystian Sienkiewicz <krystian.sienkiewicz@swmansion.com>
# Summary

Add custom style for foreground and background colors handling

## Test Plan

- Run the example web app and experiment with applying foreground and
background colors through the toolbar across different text styles.
- Run yarn test:e2e:web to see if all the tests pass

## Screenshots / Videos


https://github.com/user-attachments/assets/5490888b-7f2b-431d-99b0-36c15da1fd7d


## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |    ❌     |
| Android |    ❌     |
| Web |    ✅     |

## Checklist

- [X] E2E tests are passing
- [x] Required E2E tests have been added (if applicable)

---------

Co-authored-by: Krystian Sienkiewicz <krystian.sienkiewicz@swmansion.com>
# Summary

Add fontSize and fontFamily to customStyle

## Test Plan
Run example app and experiment with applying `fontSize` and `fontFamily`
through the toolbar across different text styles.

## Screenshots / Videos


https://github.com/user-attachments/assets/20adf200-2661-44bf-a931-1948e1419afa


## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |    ✅     |
| Android |    ❌     |
| Web     |    ❌     |

## Checklist

- [x] E2E tests are passing
- [x] Required E2E tests have been added (if applicable)

---------

Co-authored-by: Krystian Sienkiewicz <krystian.sienkiewicz@swmansion.com>
# Summary

Add fontSize and fontFamily to customStyle

## Test Plan
Run example app and experiment with applying `fontSize` and `fontFamily`
through the toolbar across different text styles.

## Screenshots / Videos


## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |    ❌     |
| Android |    ✅     |
| Web     |    ❌     |

## Checklist

- [x] E2E tests are passing
- [X] Required E2E tests have been added (if applicable)

---------

Co-authored-by: Krystian Sienkiewicz <krystian.sienkiewicz@swmansion.com>
# Summary

Add fontSize and font Family to customStyle

## Test Plan

Run example web app and experiment with applying `fontSize` and
`fontFamily` through the toolbar across different text styles.

## Screenshots / Videos



https://github.com/user-attachments/assets/29268496-e19d-4fb0-a8ea-2cc4e4c850dd


## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |    ❌     |
| Android |    ❌     |
| Web     |    ✅     |

## Checklist

- [X] E2E tests are passing
- [X] Required E2E tests have been added (if applicable)

---------

Co-authored-by: Krystian Sienkiewicz <krystian.sienkiewicz@swmansion.com>
Copilot AI balanced review requested due to automatic review settings October 16, 2026 08:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved HTML escaping, style preservation, Unicode deletion, and layout defects block approval.

18 open findings
What changed in this PR

Adds custom colors, font sizes, and font families to the rich-text editor and viewer across iOS, Android, and web.

Changes:

  • Introduces setStyle() and custom-style selection state.
  • Extends platform rendering, HTML handling, and font scaling.
  • Adds example controls and custom-style regression tests.
File Description
src/​web/​utils/​useBaseFontFamily.ts Reads inherited fonts.
src/​web/​tiptapWatchers/​useOnChangeState.ts Reports custom-style state.
src/​web/​styleConversion/​enrichedStyleToCSSVariables.ts Defines font fallback variables.
src/​web/​pmPlugins/​StripMarksInCodeBlockPlugin.ts Preserves custom-style marks.
src/​web/​normalization/​tiptapHtmlNormalizer.ts Normalizes exported colors.
src/​web/​normalization/​prepareHtmlForWeb.ts Adds viewer font fallbacks.
src/​web/​normalization/​htmlNormalizer.ts Preserves custom span styles.
src/​web/​normalization/​colorNormalizer.ts Converts color representations.
src/​web/​formats/​EnrichedCustomStyle.ts Implements custom-style marks.
src/​web/​EnrichedTextInput.tsx Connects styling commands and fonts.
src/​web/​EnrichedText.tsx Supplies viewer font fallbacks.
src/​web/​EnrichedText.css Adjusts code backgrounds.
src/​types.ts Exposes styling API and state.
src/​spec/​EnrichedTextInputNativeComponent.ts Extends native bridge contracts.
src/​native/​EnrichedTextInput.tsx Serializes native styling commands.
ios/​utils/​StyleUtils.mm Registers custom styles.
ios/​textHtmlParser/​TextHtmlParser.mm Applies parsed viewer styles.
ios/​styles/​EnrichedTextStyles.mm Implements viewer style subclass.
ios/​styles/​CustomStyle.mm Applies and merges custom styles.
ios/​interfaces/​StyleTypeEnum.h Adds custom-style identifier.
ios/​interfaces/​StyleHeaders.h Declares custom-style methods.
ios/​interfaces/​StyleBase.mm Clarifies priority comments.
ios/​interfaces/​EnrichedTextStyleHeaders.h Declares viewer style subclass.
ios/​interfaces/​CustomStyleData.mm Implements style data operations.
ios/​interfaces/​CustomStyleData.h Defines custom-style data.
ios/​inputHtmlParser/​InputHtmlParser.mm Applies imported editor styles.
ios/​htmlParser/​HtmlParser.mm Parses and serializes custom spans.
ios/​extensions/​ColorExtension.mm Adds CSS color conversion.
ios/​extensions/​ColorExtension.h Declares color conversion methods.
ios/​EnrichedTextInputView.mm Handles styling commands and events.
ios/​config/​EnrichedConfig.mm Centralizes font scaling.
ios/​config/​EnrichedConfig.h Exposes font scaling helper.
cpp/​tests/​GumboParserTest.cpp Tests colored-span normalization.
cpp/​parser/​GumboNormalizer.c Preserves colored spans.
apps/​example/​src/​screens/​TestScreen.tsx Adds keyboard-aware padding.
apps/​example/​src/​hooks/​useKeyboardHeight.ts Tracks keyboard height.
apps/​example/​src/​constants/​editorConfig.ts Initializes custom-style state.
apps/​example/​src/​components/​Toolbar.tsx Adds styling controls.
apps/​example/​src/​components/​FontSizePickerRow.tsx Adds font-size picker.
apps/​example/​src/​components/​FontFamilyPickerRow.tsx Adds font-family picker.
apps/​example/​src/​components/​ColorPickerRow.tsx Adds color picker.
apps/​example/​ios/​Podfile.lock Updates native package lock.
apps/​example-web/​src/​components/​Toolbar.tsx Adds web styling controls.
apps/​example-web/​src/​components/​Toolbar.css Styles web pickers.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​watchers/​EnrichedTextWatcher.kt Applies styles during editing.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​utils/​EnrichedSpanState.kt Reports custom-style values.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​utils/​EnrichedSelection.kt Reads selection styles.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​styles/​HtmlStyle.kt Reuses background-opacity helper.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​styles/​CustomStyles.kt Applies and merges style updates.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​spans/​EnrichedSpans.kt Registers custom spans.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​spans/​EnrichedInputCustomStyleSpan.kt Defines editor custom spans.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​MeasurementStore.kt Supplies font measurement configuration.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​EnrichedTextInputViewManager.kt Routes styling commands.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​EnrichedTextInputView.kt Connects custom-style handling.
android/​src/​main/​java/​com/​swmansion/​enriched/​textinput/​EnrichedTextInputSpannableFactory.kt Creates editor custom spans.
android/​src/​main/​java/​com/​swmansion/​enriched/​text/​spans/​EnrichedTextCustomStyleSpan.kt Defines viewer custom spans.
android/​src/​main/​java/​com/​swmansion/​enriched/​text/​MeasurementStore.kt Supplies viewer font configuration.
android/​src/​main/​java/​com/​swmansion/​enriched/​text/​EnrichedTextView.kt Configures viewer span factories.
android/​src/​main/​java/​com/​swmansion/​enriched/​text/​EnrichedTextStyle.kt Reuses background-opacity helper.
android/​src/​main/​java/​com/​swmansion/​enriched/​text/​EnrichedTextSpanFactory.kt Creates viewer custom spans.
android/​src/​main/​java/​com/​swmansion/​enriched/​common/​spans/​EnrichedCustomStyleSpan.kt Renders custom fonts and colors.
android/​src/​main/​java/​com/​swmansion/​enriched/​common/​parser/​EnrichedSpanFactory.kt Extends span creation contract.
android/​src/​main/​java/​com/​swmansion/​enriched/​common/​parser/​EnrichedParser.java Imports and exports custom styles.
android/​src/​main/​java/​com/​swmansion/​enriched/​common/​parser/​EnrichedColorParser.kt Converts CSS colors.
android/​src/​main/​java/​com/​swmansion/​enriched/​common/​EnrichedSpanFlags.kt Defines custom-style priority.
android/​src/​main/​java/​com/​swmansion/​enriched/​common/​CustomStyle.kt Defines custom-style values.
android/​src/​main/​java/​com/​swmansion/​enriched/​common/​ColorUtils.kt Shares background-opacity logic.
.playwright/​tests/​customStyleFonts.spec.ts Tests web font styling.
.playwright/​tests/​customStyleColors.spec.ts Tests web color styling.
.maestro/​enrichedText/​flows/​custom_style_fonts_visual.yaml Checks native viewer fonts.
.maestro/​enrichedText/​flows/​custom_style_colors_visual.yaml Checks native viewer colors.
.maestro/​enrichedInput/​flows/​custom_style_fonts_visual.yaml Checks native editor fonts.
.maestro/​enrichedInput/​flows/​custom_style_colors_visual.yaml Checks native editor colors.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

cssProps.append("font-family: ").append(fontFamily).append(";");
}
}
out.append("<span style=\"").append(cssProps).append("\">");
Comment on lines +869 to +881
if (has_fg) {
buffer_append_str(out, "color: ");
buffer_append(out, fg, fg_len);
}

if (has_fg && has_bg) {
buffer_append_str(out, "; ");
}

if (has_bg) {
buffer_append_str(out, "background-color: ");
buffer_append(out, bg, bg_len);
}
}
}
if (cssProps.length > 0) {
return [NSString stringWithFormat:@"span style=\"%@\"", cssProps];
Comment on lines +1028 to +1030
if (!new CustomStyle(fg, bg, fontSize, fontFamily).isEmpty()) {
start(text, new CustomStyleMark(fg, bg, fontSize, fontFamily));
}

private static String parseCssFontFamily(String raw) {
if (raw == null) return null;
String value = raw.trim();
r = ((value >> 8) & 0xF) / 15.0;
g = ((value >> 4) & 0xF) / 15.0;
b = (value & 0xF) / 15.0;
} else if (len == 6) {
Comment on lines +840 to +841
} else if ([tagName isEqualToString:@"span"]) {
CustomStyleData *data = [self parseCustomStyleDataFromSpanParams:params];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
styleAttrRegex = [NSRegularExpression
regularExpressionWithPattern:@"style\\s*=\\s*[\"']([^\"']*)[\"']"
Comment on lines +108 to +109
tag: 'span',
getAttrs: (el: HTMLElement) => {
// Cursor sits right at the plain-styled boundary.
// Handle deletion ourselves so Chrome's contenteditable
// normalization never touches the styled <span>.
const tr = state.tr.delete($from.pos - 1, $from.pos);

@exploIF exploIF left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recent commits look good. Previous were already approved.

Please go through Copilot comments and address them if they make sense

# Summary

Added documentation around the new `custom style` feature

---------

Co-authored-by: Igor Furgała <74370735+exploIF@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants