-
-
Notifications
You must be signed in to change notification settings - Fork 591
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support "start" attribute in ol and ul elements
This commit is a substantial refactor of lists, which now use @jsamr/react-native-li library to display markers with wide and rich generation algorithms. fix #336
- Loading branch information
Showing
17 changed files
with
413 additions
and
431 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
/* eslint-disable react-native/no-inline-styles */ | ||
import { Text, View, ViewStyle } from 'react-native'; | ||
import React from 'react'; | ||
import { TBlock, TNode } from '@native-html/transient-render-engine'; | ||
import { | ||
MarkedListItem, | ||
useMarkedList, | ||
MarkerBoxProps | ||
} from '@jsamr/react-native-li'; | ||
import { DefaultTagRendererProps, TChildProps } from '../shared-types'; | ||
import { useTChildrenRenderer } from '../context/TChildrenRendererContext'; | ||
import { DEFAULT_TEXT_COLOR } from '../constants'; | ||
import pick from 'ramda/src/pick'; | ||
import defaultMarkers, { UnitaryListStyleSpec } from './defaultMarkers'; | ||
import { SupportedListStyleType } from './list-types'; | ||
|
||
export interface ListElementProps<T extends 'ol' | 'ul'> | ||
extends DefaultTagRendererProps<TBlock> { | ||
listType: T; | ||
getListStyleTypeFromNestLevel: (nestLevel: number) => SupportedListStyleType; | ||
getStyleFromNestLevel?: (nestLevel: number) => ViewStyle | null; | ||
} | ||
|
||
function getStartIndex(tnode: TNode) { | ||
const parsedIndex = tnode.attributes.start | ||
? Number(tnode.attributes.start) | ||
: Number.NaN; | ||
return Number.isNaN(parsedIndex) ? 1 : parsedIndex; | ||
} | ||
|
||
function createSymbolicMarkerRenderer( | ||
component: UnitaryListStyleSpec['Component'] | ||
) { | ||
return ({ | ||
style, | ||
markerTextStyle, | ||
counterRenderer, | ||
markerTextWidth, | ||
rtlMarkerReversed = false | ||
}: MarkerBoxProps) => { | ||
const prefix = counterRenderer.renderPrefix(); | ||
const suffix = counterRenderer.renderSuffix(); | ||
return ( | ||
<View | ||
style={[ | ||
style, | ||
{ | ||
flexDirection: rtlMarkerReversed ? 'row-reverse' : 'row', | ||
justifyContent: 'flex-end', | ||
width: markerTextWidth | ||
} | ||
]}> | ||
{prefix !== '' && <Text style={markerTextStyle}>{prefix}</Text>} | ||
{React.createElement(component, markerTextStyle as any)} | ||
{suffix !== '' && <Text style={markerTextStyle}>{suffix}</Text>} | ||
</View> | ||
); | ||
}; | ||
} | ||
|
||
function extractMarkerTextStyle(tnode: TNode) { | ||
return Object.assign( | ||
{}, | ||
{ | ||
lineHeight: 14 * 1.3, | ||
fontSize: 14, | ||
color: DEFAULT_TEXT_COLOR | ||
}, | ||
pick( | ||
[ | ||
'fontStyle', | ||
'fontWeight', | ||
'fontFamily', | ||
'fontVariant', | ||
'color', | ||
'lineHeight' | ||
], | ||
tnode.styles.nativeTextFlow | ||
) | ||
); | ||
} | ||
|
||
export default function ListElement({ | ||
tnode, | ||
TDefaultRenderer, | ||
listType, | ||
style, | ||
getListStyleTypeFromNestLevel, | ||
getStyleFromNestLevel, | ||
markers, | ||
...props | ||
}: ListElementProps<any>) { | ||
const nestLevel = | ||
listType === 'ol' ? markers.olNestLevel : markers.ulNestLevel; | ||
const TChildrenRenderer = useTChildrenRenderer(); | ||
const rtl = | ||
tnode.styles.nativeBlockFlow.direction === 'rtl' || | ||
markers.direction === 'rtl'; | ||
const nestLevelStyle = getStyleFromNestLevel?.call(null, nestLevel); | ||
const selectedListType = getListStyleTypeFromNestLevel(nestLevel); | ||
const listStyleType = | ||
(tnode.styles.webTextFlow.listStyleType as SupportedListStyleType) || | ||
selectedListType; | ||
if (__DEV__ && !(listStyleType in defaultMarkers)) { | ||
console.warn( | ||
`list-style-type "${listStyleType}" is not handled by react-native-render-html.` + | ||
'You can register a custom list marker renderer with the appropriate prop.' | ||
); | ||
} | ||
const spec = | ||
listStyleType in defaultMarkers | ||
? defaultMarkers[listStyleType] | ||
: defaultMarkers[selectedListType]; | ||
const counterRenderer = spec.counterStyleRenderer; | ||
const startIndex = getStartIndex(tnode); | ||
const markerTextStyle = extractMarkerTextStyle(tnode); | ||
const itemProps = useMarkedList({ | ||
counterRenderer, | ||
startIndex, | ||
markerTextStyle, | ||
rtlLineReversed: rtl, | ||
rtlMarkerReversed: rtl, | ||
length: tnode.children.length, | ||
renderMarker: | ||
spec.type === 'cyclic' | ||
? createSymbolicMarkerRenderer(spec.Component) | ||
: undefined | ||
}); | ||
const markerWidth = itemProps.markerTextWidth; | ||
const renderChild = ({ childElement, key, index }: TChildProps) => ( | ||
<MarkedListItem | ||
key={key} | ||
index={index} | ||
{...itemProps} | ||
style={[itemProps.style, { [rtl ? 'right' : 'left']: -markerWidth }]}> | ||
<View style={{ flexShrink: 1 }}>{childElement}</View> | ||
</MarkedListItem> | ||
); | ||
const fixedPaddingRule = rtl | ||
? ('paddingRight' as const) | ||
: ('paddingLeft' as const); | ||
const paddingValue = tnode.styles.nativeBlockRet[fixedPaddingRule]; | ||
const dynamicStyle = { | ||
position: 'relative' as const, | ||
[fixedPaddingRule]: | ||
typeof paddingValue === 'number' | ||
? Math.max(paddingValue, markerWidth) | ||
: markerWidth | ||
}; | ||
return ( | ||
<TDefaultRenderer | ||
tnode={tnode} | ||
markers={markers} | ||
style={[style, nestLevelStyle, dynamicStyle]} | ||
{...props}> | ||
<TChildrenRenderer | ||
tchildren={tnode.children} | ||
renderChild={renderChild} | ||
parentMarkers={markers} | ||
/> | ||
</TDefaultRenderer> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,7 @@ | ||
import React from 'react'; | ||
import GenericListElement, { | ||
GenericListElementProps | ||
} from './GenericListElement'; | ||
import ListElement, { ListElementProps } from './ListElement'; | ||
|
||
export type OLElementProps = Omit<GenericListElementProps<'ol'>, 'listType'>; | ||
export type OLElementProps = Omit<ListElementProps<'ol'>, 'listType'>; | ||
export default function OLElement(props: OLElementProps) { | ||
return React.createElement(GenericListElement, { ...props, listType: 'ol' }); | ||
return React.createElement(ListElement, { ...props, listType: 'ol' }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,8 @@ | ||
import React from 'react'; | ||
import GenericListElement, { | ||
GenericListElementProps | ||
} from './GenericListElement'; | ||
import ListElement, { ListElementProps } from './ListElement'; | ||
|
||
export type ULElementProps = Omit<ListElementProps<'ul'>, 'listType'>; | ||
|
||
export type ULElementProps = Omit<GenericListElementProps<'ul'>, 'listType'>; | ||
export default function ULElement(props: ULElementProps) { | ||
return React.createElement(GenericListElement, { ...props, listType: 'ul' }); | ||
return React.createElement(ListElement, { ...props, listType: 'ul' }); | ||
} |
7 changes: 0 additions & 7 deletions
7
packages/render-html/src/elements/__tests__/numOfCharsInPrefix.test.ts
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import CounterStyle, { CounterStyleRenderer } from '@jsamr/counter-style'; | ||
import { ComponentType } from 'react'; | ||
import decimal from '@jsamr/counter-style/presets/decimal'; | ||
import decimalLeadingZero from '@jsamr/counter-style/presets/decimalLeadingZero'; | ||
import lowerAlpha from '@jsamr/counter-style/presets/lowerAlpha'; | ||
import lowerGreek from '@jsamr/counter-style/presets/lowerGreek'; | ||
import upperAlpha from '@jsamr/counter-style/presets/upperAlpha'; | ||
import DisclosureClosedSymbolRenderer from './symbolic/DisclosureClosedSymbolRenderer'; | ||
import DisclosureOpenSymbolRenderer from './symbolic/DisclosureOpenSymbolRenderer'; | ||
import CircleSymbolRenderer from './symbolic/CircleSymbolRenderer'; | ||
import DiscSymbolRenderer from './symbolic/DiscSymbolRenderer'; | ||
import SquareSymbolRenderer from './symbolic/SquareSymbolRenderer'; | ||
import { ListPrefixRendererProps, SupportedListStyleType } from './list-types'; | ||
|
||
const symbolicRenderer = CounterStyle.cyclic('*').withSuffix(' '); | ||
|
||
/** | ||
* Specs for a list item marker renderer backed by a `CounterStyleRenderer` | ||
* from `@jsamr/counter-style`. | ||
*/ | ||
export interface TextualListStyleSpec { | ||
type: 'textual'; | ||
counterStyleRenderer: CounterStyleRenderer; | ||
} | ||
|
||
/** | ||
* Specs for a list item marker renderer with only one representation. The | ||
* "Component" should render this representation, minus prefix and suffix. The | ||
* rendered component should have a maximum width of `0.6 * fontSize`, and a height of | ||
* `lineHeight`. | ||
*/ | ||
export interface UnitaryListStyleSpec { | ||
counterStyleRenderer: CounterStyleRenderer; | ||
type: 'cyclic'; | ||
Component: ComponentType<ListPrefixRendererProps>; | ||
} | ||
|
||
export type ListStyleSpec = TextualListStyleSpec | UnitaryListStyleSpec; | ||
|
||
const lowerAlphaSpec = { | ||
type: 'textual', | ||
counterStyleRenderer: lowerAlpha | ||
} as const; | ||
|
||
const upperAlphaSpec = { | ||
type: 'textual', | ||
counterStyleRenderer: upperAlpha | ||
} as const; | ||
|
||
const defaultMarkers: Record<SupportedListStyleType, ListStyleSpec> = { | ||
'decimal-leading-zero': { | ||
type: 'textual', | ||
counterStyleRenderer: decimalLeadingZero | ||
}, | ||
'disclosure-closed': { | ||
counterStyleRenderer: symbolicRenderer, | ||
type: 'cyclic', | ||
Component: DisclosureClosedSymbolRenderer | ||
}, | ||
'disclosure-open': { | ||
counterStyleRenderer: symbolicRenderer, | ||
type: 'cyclic', | ||
Component: DisclosureOpenSymbolRenderer | ||
}, | ||
'lower-alpha': lowerAlphaSpec, | ||
'lower-greek': { | ||
type: 'textual', | ||
counterStyleRenderer: lowerGreek | ||
}, | ||
'lower-latin': lowerAlphaSpec, | ||
'upper-alpha': upperAlphaSpec, | ||
'upper-latin': upperAlphaSpec, | ||
circle: { | ||
counterStyleRenderer: symbolicRenderer, | ||
type: 'cyclic', | ||
Component: CircleSymbolRenderer | ||
}, | ||
decimal: { | ||
type: 'textual', | ||
counterStyleRenderer: decimal | ||
}, | ||
disc: { | ||
counterStyleRenderer: symbolicRenderer, | ||
type: 'cyclic', | ||
Component: DiscSymbolRenderer | ||
}, | ||
none: { | ||
counterStyleRenderer: CounterStyle.symbolic('').withSuffix(null), | ||
type: 'cyclic', | ||
Component: () => null | ||
}, | ||
square: { | ||
counterStyleRenderer: symbolicRenderer, | ||
type: 'cyclic', | ||
Component: SquareSymbolRenderer | ||
} | ||
}; | ||
|
||
export default defaultMarkers; |
Oops, something went wrong.