@onekeyfe/react-native-native-list 6.7.0-alpha.430 → 6.7.0-alpha.432

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.
@@ -30,79 +30,6 @@ function toneColor(
30
30
  if (tone === 'secondary') return 'var(--nl-secondary)';
31
31
  return fallback === 'secondary' ? 'var(--nl-secondary)' : 'var(--nl-primary)';
32
32
  }
33
- // Hidden from sight, still read: the runs themselves are hidden from
34
- // assistive technology in favor of `title`.
35
- const VISUALLY_HIDDEN: Partial<CSSStyleDeclaration> = {
36
- position: 'absolute',
37
- width: '1px',
38
- height: '1px',
39
- overflow: 'hidden',
40
- clip: 'rect(0 0 0 0)',
41
- whiteSpace: 'nowrap',
42
- };
43
- function appendTitleRuns(
44
- document: Document,
45
- target: HTMLElement,
46
- row: IdentityRow
47
- ): void {
48
- const runs = createElement(
49
- document,
50
- 'span',
51
- 'ok-native-list-identity-title-runs'
52
- );
53
- runs.setAttribute('aria-hidden', 'true');
54
- Object.assign(runs.style, {
55
- display: 'flex',
56
- flex: '1 1 auto',
57
- minWidth: '0',
58
- maxWidth: '100%',
59
- overflow: 'hidden',
60
- });
61
- row.titleSegments?.forEach((segment) => {
62
- const run = createElement(
63
- document,
64
- 'span',
65
- 'ok-native-list-identity-title-run',
66
- segment.text
67
- );
68
- // Only the shrinking run gives up width; a cap is a share of the line.
69
- Object.assign(run.style, {
70
- flex: segment.shrink ? '0 1 auto' : '0 0 auto',
71
- minWidth: segment.shrink ? '0' : '',
72
- maxWidth:
73
- segment.maxWidthRatio === undefined
74
- ? ''
75
- : `${segment.maxWidthRatio * 100}%`,
76
- overflow: 'hidden',
77
- textOverflow: 'ellipsis',
78
- whiteSpace: 'pre',
79
- });
80
- if (segment.tone)
81
- run.style.color =
82
- segment.tone === 'secondary'
83
- ? 'var(--nl-secondary)'
84
- : 'var(--nl-primary)';
85
- if (segment.fontWeight === 'medium') {
86
- run.style.fontWeight = '500';
87
- run.style.fontFamily = 'var(--nl-font-medium)';
88
- } else if (segment.fontWeight === 'regular') {
89
- run.style.fontWeight = '400';
90
- run.style.fontFamily = 'var(--nl-font-regular)';
91
- }
92
- setData(run, 'tone', segment.tone);
93
- runs.appendChild(run);
94
- });
95
- const spoken = createElement(
96
- document,
97
- 'span',
98
- 'ok-native-list-identity-title-spoken',
99
- row.title
100
- );
101
- Object.assign(spoken.style, VISUALLY_HIDDEN);
102
- target.replaceChildren(runs, spoken);
103
- target.style.display = 'flex';
104
- target.style.minWidth = '0';
105
- }
106
33
  function appendAccessories(
107
34
  parent: HTMLElement,
108
35
  document: Document,
@@ -268,18 +195,6 @@ function createIdentityRow(
268
195
  fragment.appendChild(document.createTextNode(row.title.slice(offset)));
269
196
  textTarget.prepend(fragment);
270
197
  }
271
- // The runs replace the plain title, except where `title` itself is
272
- // highlighted or shown in the wallet sidebar.
273
- const hasTitleRuns =
274
- !!row.titleSegments?.length &&
275
- presentation !== 'walletSidebar' &&
276
- !row.titleMatch?.length;
277
- if (hasTitleRuns) {
278
- const textTarget =
279
- titleElement.querySelector<HTMLElement>('[data-nl-slot="title"]') ??
280
- titleElement;
281
- appendTitleRuns(document, textTarget, row);
282
- }
283
198
  if (row.subtitleSegments?.length) {
284
199
  column.querySelector('.ok-native-list-secondary')?.remove();
285
200
  const segments = createElement(
@@ -476,58 +391,6 @@ function createIdentityRow(
476
391
  badges.style.minWidth = '0';
477
392
  }
478
393
  }
479
- if (hasTitleRuns) {
480
- // The runs fill what the title line leaves, so a ratio is a share of
481
- // that width and title accessories sit at the line's end.
482
- const runs = titleElement.querySelector<HTMLElement>(
483
- '.ok-native-list-identity-title-runs'
484
- );
485
- for (
486
- let node = runs?.parentElement;
487
- node && node !== titleElement;
488
- node = node.parentElement
489
- ) {
490
- node.style.display = 'flex';
491
- node.style.flex = '1 1 auto';
492
- node.style.minWidth = '0';
493
- }
494
- titleElement.style.display = 'flex';
495
- titleElement.style.alignItems = 'center';
496
- }
497
- if (row.titleLeadingIcon && presentation !== 'walletSidebar') {
498
- const descriptor = row.titleLeadingIcon;
499
- if (titleElement.hasAttribute('data-nl-slot')) {
500
- // Keep the glyph out of the title slot that text styles apply to.
501
- const titleText = tagSlot(
502
- createElement(document, 'span', 'ok-native-list-identity-title-text'),
503
- 'title'
504
- );
505
- while (titleElement.firstChild)
506
- titleText.appendChild(titleElement.firstChild);
507
- if (hasTitleRuns) {
508
- titleText.style.display = 'flex';
509
- titleText.style.minWidth = '0';
510
- }
511
- titleElement.removeAttribute('data-nl-slot');
512
- titleElement.appendChild(titleText);
513
- }
514
- const icon = primitives.iconAction(
515
- descriptor.name,
516
- undefined,
517
- undefined,
518
- descriptor.tintColor
519
- );
520
- icon.className = 'ok-native-list-identity-title-leading-icon';
521
- if (descriptor.accessibilityLabel)
522
- icon.setAttribute('aria-label', descriptor.accessibilityLabel);
523
- else icon.setAttribute('aria-hidden', 'true');
524
- titleElement.style.display = 'flex';
525
- titleElement.style.alignItems = 'center';
526
- // 6px to the title, whatever gap the title line already has.
527
- const lineGap = Number.parseFloat(titleElement.style.gap || '0') || 0;
528
- icon.style.marginInlineEnd = `${Math.max(0, 6 - lineGap)}px`;
529
- titleElement.prepend(icon);
530
- }
531
394
  if (presentation === 'walletSidebar' && row.badges?.length) {
532
395
  const badges = createElement(
533
396
  document,
@@ -540,7 +403,6 @@ function createIdentityRow(
540
403
  column.appendChild(badges);
541
404
  }
542
405
  for (const key of ['title', 'subtitle'] as const) {
543
- if (key === 'title' && hasTitleRuns) continue;
544
406
  const lines = row.style?.[key]?.lines ?? row[`${key}Lines`];
545
407
  if (lines !== undefined)
546
408
  column
@@ -1,38 +0,0 @@
1
- package com.margelo.nitro.nativelist
2
-
3
- /**
4
- * Widths for identity title runs (docs/SPEC.md, "Identity title segments and
5
- * leading title icon"). A run's base width is its natural width under its cap;
6
- * the one shrinking run takes what the others leave; runs that do not shrink
7
- * and still overflow are cut from the end of the line.
8
- */
9
- internal fun nativeListTitleSegmentWidths(
10
- natural: List<Float>,
11
- shrink: List<Boolean>,
12
- maxWidthRatio: List<Float?>,
13
- available: Float,
14
- ): List<Float> {
15
- val line = available.coerceAtLeast(0f)
16
- val widths =
17
- natural.indices
18
- .map { index ->
19
- val width = natural[index].coerceAtLeast(0f)
20
- val ratio = maxWidthRatio.getOrNull(index)
21
- if (ratio == null) width else minOf(width, ratio * line)
22
- }
23
- .toMutableList()
24
- val shrinking = shrink.indexOf(true)
25
- if (shrinking in widths.indices) {
26
- val others = widths.indices.filter { it != shrinking }.sumOf { widths[it].toDouble() }.toFloat()
27
- widths[shrinking] = minOf(widths[shrinking], line - others).coerceAtLeast(0f)
28
- }
29
- var overflow = widths.sum() - line
30
- var index = widths.lastIndex
31
- while (overflow > 0f && index >= 0) {
32
- val cut = minOf(widths[index], overflow)
33
- widths[index] -= cut
34
- overflow -= cut
35
- index -= 1
36
- }
37
- return widths
38
- }
@@ -1,105 +0,0 @@
1
- package com.margelo.nitro.nativelist
2
-
3
- import android.graphics.Typeface
4
- import android.text.SpannableStringBuilder
5
- import android.text.Spanned
6
- import android.text.TextPaint
7
- import android.text.TextUtils
8
- import android.text.style.ForegroundColorSpan
9
- import android.text.style.MetricAffectingSpan
10
-
11
- /**
12
- * Identity title runs on one line. The text is rebuilt for each measured width
13
- * from the widths `nativeListTitleSegmentWidths` allocates, so a run gives up
14
- * width without the others; accessibility reads the row's `title` instead.
15
- */
16
- internal class NativeListTitleSegmentsView(context: android.content.Context) :
17
- NativeListTextView(context) {
18
- class Run(
19
- val text: String,
20
- // Null keeps the view's typeface, which follows the title's.
21
- val typeface: Typeface?,
22
- val color: Int,
23
- val shrink: Boolean,
24
- val maxWidthRatio: Float?,
25
- )
26
-
27
- private var runs: List<Run> = emptyList()
28
- private var builtForWidth = -1
29
-
30
- fun setRuns(runs: List<Run>, spokenText: String) {
31
- this.runs = runs
32
- builtForWidth = -1
33
- contentDescription = spokenText.takeIf(String::isNotEmpty)
34
- text = runs.joinToString("") { it.text }
35
- requestLayout()
36
- }
37
-
38
- fun clearRuns() {
39
- runs = emptyList()
40
- builtForWidth = -1
41
- contentDescription = null
42
- text = ""
43
- }
44
-
45
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
46
- if (runs.isEmpty()) {
47
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
48
- return
49
- }
50
- var outer =
51
- if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) Int.MAX_VALUE
52
- else MeasureSpec.getSize(widthMeasureSpec)
53
- if (maxWidth in 0 until outer) outer = maxWidth
54
- val available =
55
- if (outer == Int.MAX_VALUE) outer
56
- else (outer - compoundPaddingLeft - compoundPaddingRight).coerceAtLeast(0)
57
- if (available != builtForWidth) {
58
- builtForWidth = available
59
- // The same width rebuilds the same text, so the relayout this asks for
60
- // measures once more and stops.
61
- setText(build(available), BufferType.SPANNABLE)
62
- }
63
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
64
- // The runs fill what the title line leaves, so a ratio is a share of it.
65
- if (outer != Int.MAX_VALUE) setMeasuredDimension(outer, measuredHeight)
66
- }
67
-
68
- private fun build(available: Int): CharSequence {
69
- val paints = runs.map { run -> TextPaint(paint).apply { run.typeface?.let { typeface = it } } }
70
- val natural = runs.mapIndexed { index, run -> paints[index].measureText(run.text) }
71
- val widths =
72
- nativeListTitleSegmentWidths(
73
- natural,
74
- runs.map { it.shrink },
75
- runs.map { it.maxWidthRatio },
76
- if (available == Int.MAX_VALUE) Float.MAX_VALUE else available.toFloat(),
77
- )
78
- val built = SpannableStringBuilder()
79
- runs.forEachIndexed { index, run ->
80
- val width = widths[index]
81
- if (width <= 0f) return@forEachIndexed
82
- val shown =
83
- if (width >= natural[index]) run.text
84
- else TextUtils.ellipsize(run.text, paints[index], width, TextUtils.TruncateAt.END)
85
- if (shown.isEmpty()) return@forEachIndexed
86
- val start = built.length
87
- built.append(shown)
88
- built.setSpan(ForegroundColorSpan(run.color), start, built.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
89
- run.typeface?.let {
90
- built.setSpan(TypefaceRunSpan(it), start, built.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
91
- }
92
- }
93
- return built
94
- }
95
- }
96
-
97
- private class TypefaceRunSpan(private val typeface: Typeface) : MetricAffectingSpan() {
98
- override fun updateMeasureState(paint: TextPaint) {
99
- paint.typeface = typeface
100
- }
101
-
102
- override fun updateDrawState(paint: TextPaint) {
103
- paint.typeface = typeface
104
- }
105
- }
@@ -1,65 +0,0 @@
1
- package com.margelo.nitro.nativelist
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Test
5
-
6
- class NativeListTitleSegmentLayoutTest {
7
- // Wallet / Account as recipient rows send it: the wallet shrinks, the
8
- // account is capped at 70% of the line.
9
- private fun walletFirst(wallet: Float, account: Float, line: Float) =
10
- nativeListTitleSegmentWidths(
11
- listOf(wallet, 20f, account),
12
- listOf(true, false, false),
13
- listOf(null, null, 0.7f),
14
- line,
15
- )
16
-
17
- private fun assertWidths(expected: List<Float>, actual: List<Float>) {
18
- assertEquals(expected.size, actual.size)
19
- expected.zip(actual).forEach { (want, got) -> assertEquals(want, got, 0.001f) }
20
- }
21
-
22
- @Test
23
- fun keepsNaturalWidthsWhenTheyFit() = assertWidths(listOf(80f, 20f, 90f), walletFirst(80f, 90f, 300f))
24
-
25
- @Test
26
- fun theShrinkingRunGivesWayFirst() = assertWidths(listOf(130f, 20f, 100f), walletFirst(200f, 100f, 250f))
27
-
28
- @Test
29
- fun aCappedRunStopsAtItsShareOfTheLine() = assertWidths(listOf(70f, 20f, 210f), walletFirst(100f, 400f, 300f))
30
-
31
- @Test
32
- fun theAccountShrinksWhenTheWalletMustStayWhole() =
33
- assertWidths(
34
- listOf(210f, 20f, 70f),
35
- nativeListTitleSegmentWidths(
36
- listOf(300f, 20f, 150f),
37
- listOf(false, false, true),
38
- listOf(0.7f, null, null),
39
- 300f,
40
- ),
41
- )
42
-
43
- @Test
44
- fun runsThatDoNotShrinkAreCutFromTheEnd() {
45
- assertWidths(
46
- listOf(100f, 50f, 0f),
47
- nativeListTitleSegmentWidths(
48
- listOf(100f, 100f, 100f),
49
- listOf(false, false, true),
50
- listOf(null, null, null),
51
- 150f,
52
- ),
53
- )
54
- assertWidths(
55
- listOf(100f, 20f),
56
- nativeListTitleSegmentWidths(listOf(100f, 100f), listOf(false, false), listOf(null, null), 120f),
57
- )
58
- }
59
-
60
- @Test
61
- fun aNonPositiveLineLeavesNothing() {
62
- assertWidths(listOf(0f, 0f, 0f), walletFirst(80f, 90f, 0f))
63
- assertWidths(listOf(0f, 0f, 0f), walletFirst(80f, 90f, -10f))
64
- }
65
- }
@@ -1,32 +0,0 @@
1
- import Foundation
2
-
3
- /// Widths for identity title runs (docs/SPEC.md, "Identity title segments
4
- /// and leading title icon"). A run's base width is its natural width under
5
- /// its cap; the one shrinking run takes what the others leave; runs that do
6
- /// not shrink and still overflow are cut from the end of the line.
7
- func nativeListTitleSegmentWidths(
8
- natural: [CGFloat],
9
- shrink: [Bool],
10
- maxWidthRatio: [CGFloat?],
11
- available: CGFloat
12
- ) -> [CGFloat] {
13
- let line = max(0, available)
14
- var widths = natural.indices.map { index -> CGFloat in
15
- let width = max(0, natural[index])
16
- guard index < maxWidthRatio.count, let ratio = maxWidthRatio[index] else { return width }
17
- return min(width, ratio * line)
18
- }
19
- if let shrinking = shrink.firstIndex(of: true), shrinking < widths.count {
20
- let others = widths.indices.reduce(CGFloat(0)) { $1 == shrinking ? $0 : $0 + widths[$1] }
21
- widths[shrinking] = max(0, min(widths[shrinking], line - others))
22
- }
23
- var overflow = widths.reduce(0, +) - line
24
- var index = widths.count - 1
25
- while overflow > 0, index >= 0 {
26
- let cut = min(widths[index], overflow)
27
- widths[index] -= cut
28
- overflow -= cut
29
- index -= 1
30
- }
31
- return widths
32
- }
@@ -1,80 +0,0 @@
1
- import UIKit
2
-
3
- /// Draws identity title runs on one line, each at the width that
4
- /// `nativeListTitleSegmentWidths` allocates for the current bounds. Its
5
- /// intrinsic width is the runs' natural width, so the title row stack can
6
- /// give it less, and accessibility reads the row's `title` instead of runs.
7
- final class NativeListTitleSegmentsLabel: UIView {
8
- struct Run {
9
- let text: String
10
- let font: UIFont
11
- let color: UIColor
12
- let shrink: Bool
13
- let maxWidthRatio: CGFloat?
14
- }
15
-
16
- private var runs: [Run] = []
17
- private var naturalWidths: [CGFloat] = []
18
- var lineHeight: CGFloat = 24 {
19
- didSet {
20
- invalidateIntrinsicContentSize()
21
- setNeedsDisplay()
22
- }
23
- }
24
-
25
- override init(frame: CGRect) {
26
- super.init(frame: frame)
27
- isOpaque = false
28
- backgroundColor = .clear
29
- contentMode = .redraw
30
- isAccessibilityElement = true
31
- accessibilityTraits = .staticText
32
- }
33
-
34
- required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
35
-
36
- func configure(_ runs: [Run], spokenText: String) {
37
- self.runs = runs
38
- naturalWidths = runs.map {
39
- ceil(($0.text as NSString).size(withAttributes: [.font: $0.font]).width)
40
- }
41
- accessibilityLabel = spokenText.isEmpty ? nil : spokenText
42
- invalidateIntrinsicContentSize()
43
- setNeedsDisplay()
44
- }
45
-
46
- func reset() {
47
- configure([], spokenText: "")
48
- }
49
-
50
- override var intrinsicContentSize: CGSize {
51
- CGSize(width: naturalWidths.reduce(0, +), height: lineHeight)
52
- }
53
-
54
- override func layoutSubviews() {
55
- super.layoutSubviews()
56
- setNeedsDisplay()
57
- }
58
-
59
- override func draw(_ rect: CGRect) {
60
- guard !runs.isEmpty else { return }
61
- let widths = nativeListTitleSegmentWidths(
62
- natural: naturalWidths, shrink: runs.map(\.shrink),
63
- maxWidthRatio: runs.map(\.maxWidthRatio), available: bounds.width)
64
- let isRightToLeft = effectiveUserInterfaceLayoutDirection == .rightToLeft
65
- var x: CGFloat = isRightToLeft ? bounds.width : 0
66
- for (run, width) in zip(runs, widths) where width > 0 {
67
- let paragraph = NSMutableParagraphStyle()
68
- paragraph.lineBreakMode = .byTruncatingTail
69
- let text = NSAttributedString(
70
- string: run.text,
71
- attributes: [.font: run.font, .foregroundColor: run.color, .paragraphStyle: paragraph])
72
- let height = ceil(run.font.lineHeight)
73
- let originX = isRightToLeft ? x - width : x
74
- text.draw(
75
- with: CGRect(x: originX, y: (bounds.height - height) / 2, width: width, height: height),
76
- options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
77
- x += isRightToLeft ? -width : width
78
- }
79
- }
80
- }