@onekeyfe/react-native-native-list 3.0.157-alpha.248 → 3.0.157-alpha.250
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.
- package/README.md +21 -5
- package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +711 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActionRenderer.kt +169 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +243 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +20 -6
- package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +487 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +820 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListImageSlot.kt +147 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +453 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMarketRenderer.kt +997 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +234 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +232 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMetricCardRenderer.kt +551 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +24 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +475 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRailRenderer.kt +143 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +42 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +415 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +163 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowHost.kt +70 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +748 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListSystemRenderer.kt +464 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +25 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +174 -104
- package/android/src/main/java/com/onekey/nativelist/NativeListWalletGroupRenderer.kt +407 -0
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListSelectorBackgroundColorTest.kt +34 -0
- package/docs/DESIGN.md +518 -9
- package/docs/MIGRATION_BASELINE.md +117 -0
- package/docs/ROW_TEMPLATES.md +359 -0
- package/docs/SPEC.md +358 -0
- package/docs/STYLE_SPEC.md +978 -0
- package/docs/images/list-capabilities.svg +39 -0
- package/docs/images/row-action.svg +17 -0
- package/docs/images/row-activity.svg +24 -0
- package/docs/images/row-data.svg +24 -0
- package/docs/images/row-identity.svg +25 -0
- package/docs/images/row-market.svg +23 -0
- package/docs/images/row-media-tile.svg +26 -0
- package/docs/images/row-message.svg +20 -0
- package/docs/images/row-metric-card.svg +33 -0
- package/docs/images/row-rail.svg +18 -0
- package/docs/images/row-section-header.svg +19 -0
- package/docs/images/row-style-regions.svg +30 -0
- package/docs/images/row-system.svg +30 -0
- package/docs/images/row-wallet-group.svg +26 -0
- package/ios/NativeListAccessoryStack.swift +680 -0
- package/ios/NativeListActionRenderer.swift +108 -0
- package/ios/NativeListActivityRenderer.swift +179 -0
- package/ios/NativeListDataRowRenderer.swift +444 -0
- package/ios/NativeListIdentityRenderer.swift +685 -0
- package/ios/NativeListImageSlot.swift +140 -0
- package/ios/NativeListLeadingVisual.swift +372 -0
- package/ios/NativeListMarketRenderer.swift +698 -0
- package/ios/NativeListMediaTileRenderer.swift +184 -0
- package/ios/NativeListMessageRenderer.swift +168 -0
- package/ios/NativeListMetricCardRenderer.swift +420 -0
- package/ios/NativeListModels.swift +32 -0
- package/ios/NativeListRailRenderer.swift +152 -0
- package/ios/NativeListRendererCell.swift +311 -0
- package/ios/NativeListRendererRegistry.swift +50 -0
- package/ios/NativeListResolvedText.swift +122 -0
- package/ios/NativeListRowHost.swift +63 -0
- package/ios/NativeListSectionHeaderRenderer.swift +708 -0
- package/ios/NativeListSystemRenderer.swift +568 -0
- package/ios/NativeListTextLabel.swift +42 -0
- package/ios/NativeListTextStyles.swift +236 -0
- package/ios/NativeListWalletGroupRenderer.swift +255 -0
- package/ios/RNCNativeListView.swift +111 -180
- package/lib/module/models.js +54 -0
- package/lib/module/validation.js +324 -26
- package/lib/module/web/NativeListWebEngine.js +148 -936
- package/lib/module/web/templates/ActionRowRenderer.js +97 -0
- package/lib/module/web/templates/ActivityRowRenderer.js +95 -0
- package/lib/module/web/templates/DataRowRenderer.js +145 -0
- package/lib/module/web/templates/IdentityRowRenderer.js +289 -0
- package/lib/module/web/templates/MarketRowRenderer.js +310 -0
- package/lib/module/web/templates/MediaTileRowRenderer.js +132 -0
- package/lib/module/web/templates/MessageRowRenderer.js +165 -0
- package/lib/module/web/templates/MetricCardRowRenderer.js +172 -0
- package/lib/module/web/templates/RailRowRenderer.js +98 -0
- package/lib/module/web/templates/RowContainerStyle.js +100 -0
- package/lib/module/web/templates/RowElements.js +93 -0
- package/lib/module/web/templates/RowRendererRegistry.js +70 -0
- package/lib/module/web/templates/RowText.js +121 -0
- package/lib/module/web/templates/RowVisual.js +43 -0
- package/lib/module/web/templates/SectionHeaderRowRenderer.js +146 -0
- package/lib/module/web/templates/SystemRowRenderer.js +161 -0
- package/lib/module/web/templates/WalletGroupRowRenderer.js +96 -0
- package/lib/typescript/src/models.d.ts +166 -13
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +18 -16
- package/lib/typescript/src/web/templates/ActionRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/ActivityRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/DataRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +16 -0
- package/lib/typescript/src/web/templates/MarketRowRenderer.d.ts +30 -0
- package/lib/typescript/src/web/templates/MediaTileRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +44 -0
- package/lib/typescript/src/web/templates/MetricCardRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/RailRowRenderer.d.ts +28 -0
- package/lib/typescript/src/web/templates/RowContainerStyle.d.ts +9 -0
- package/lib/typescript/src/web/templates/RowElements.d.ts +38 -0
- package/lib/typescript/src/web/templates/RowRendererRegistry.d.ts +83 -0
- package/lib/typescript/src/web/templates/RowText.d.ts +9 -0
- package/lib/typescript/src/web/templates/RowVisual.d.ts +20 -0
- package/lib/typescript/src/web/templates/SectionHeaderRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/SystemRowRenderer.d.ts +16 -0
- package/lib/typescript/src/web/templates/WalletGroupRowRenderer.d.ts +20 -0
- package/package.json +3 -3
- package/src/models.ts +281 -19
- package/src/validation.ts +501 -46
- package/src/web/NativeListWebEngine.ts +361 -1932
- package/src/web/templates/ActionRowRenderer.ts +123 -0
- package/src/web/templates/ActivityRowRenderer.ts +159 -0
- package/src/web/templates/DataRowRenderer.ts +212 -0
- package/src/web/templates/IdentityRowRenderer.ts +527 -0
- package/src/web/templates/MarketRowRenderer.ts +467 -0
- package/src/web/templates/MediaTileRowRenderer.ts +182 -0
- package/src/web/templates/MessageRowRenderer.ts +306 -0
- package/src/web/templates/MetricCardRowRenderer.ts +337 -0
- package/src/web/templates/RailRowRenderer.ts +136 -0
- package/src/web/templates/RowContainerStyle.ts +131 -0
- package/src/web/templates/RowElements.ts +160 -0
- package/src/web/templates/RowRendererRegistry.ts +111 -0
- package/src/web/templates/RowText.ts +181 -0
- package/src/web/templates/RowVisual.ts +115 -0
- package/src/web/templates/SectionHeaderRowRenderer.ts +243 -0
- package/src/web/templates/SystemRowRenderer.ts +282 -0
- package/src/web/templates/WalletGroupRowRenderer.ts +131 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +0 -4538
- package/ios/NativeListCell.swift +0 -4300
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { NativeListTextStyle, RailRow } from '../../models';
|
|
2
|
+
import type { RowPrimitives } from './RowVisual';
|
|
3
|
+
import { RowVisualStyle } from './RowVisual';
|
|
4
|
+
|
|
5
|
+
export function resolveRailRow(row: RailRow) {
|
|
6
|
+
const style = row.style;
|
|
7
|
+
return {
|
|
8
|
+
horizontalPadding: style?.horizontalPadding ?? 4,
|
|
9
|
+
verticalPadding: style?.verticalPadding ?? 4,
|
|
10
|
+
leadingGap: style?.leadingGap ?? 6,
|
|
11
|
+
badgeGap: style?.titleBadgeGap ?? 6,
|
|
12
|
+
trailingGap: style?.trailingGap ?? 6,
|
|
13
|
+
width: style?.image?.width ?? 20,
|
|
14
|
+
height: style?.image?.height ?? 20,
|
|
15
|
+
title: row.title,
|
|
16
|
+
badge: row.badge?.text ?? '',
|
|
17
|
+
status: row.status === 'none' ? '' : row.status ?? '',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type Views = {
|
|
22
|
+
title: HTMLElement;
|
|
23
|
+
badge: HTMLElement;
|
|
24
|
+
status: HTMLElement;
|
|
25
|
+
visual?: HTMLElement;
|
|
26
|
+
visualStyle?: RowVisualStyle;
|
|
27
|
+
identity: string;
|
|
28
|
+
primitives: RowPrimitives;
|
|
29
|
+
};
|
|
30
|
+
const views = new WeakMap<HTMLElement, Views>();
|
|
31
|
+
|
|
32
|
+
function bind(body: HTMLElement, row: RailRow, primitives: RowPrimitives) {
|
|
33
|
+
const v = views.get(body)!;
|
|
34
|
+
v.primitives = primitives;
|
|
35
|
+
const r = resolveRailRow(row);
|
|
36
|
+
body.style.cssText = '';
|
|
37
|
+
body.removeAttribute('data-nl-container-background');
|
|
38
|
+
body.removeAttribute('data-nl-container-border');
|
|
39
|
+
body.style.padding = `${r.verticalPadding}px ${r.horizontalPadding}px`;
|
|
40
|
+
const text = (
|
|
41
|
+
view: HTMLElement,
|
|
42
|
+
value: string,
|
|
43
|
+
style?: NativeListTextStyle
|
|
44
|
+
) => {
|
|
45
|
+
view.style.cssText = '';
|
|
46
|
+
view.textContent = value;
|
|
47
|
+
if (style) primitives.textStyle(view, style);
|
|
48
|
+
if (value) body.appendChild(view);
|
|
49
|
+
else view.remove();
|
|
50
|
+
};
|
|
51
|
+
text(v.title, r.title, row.style?.title);
|
|
52
|
+
text(v.badge, r.badge, row.style?.badge);
|
|
53
|
+
if (row.badge?.tone) v.badge.dataset.tone = row.badge.tone;
|
|
54
|
+
else delete v.badge.dataset.tone;
|
|
55
|
+
v.badge.style.marginInlineStart = `${r.badgeGap - 6}px`;
|
|
56
|
+
text(v.status, r.status, row.style?.status);
|
|
57
|
+
v.status.style.marginInlineStart = `${r.trailingGap - 6}px`;
|
|
58
|
+
const identity = JSON.stringify([row.key, row.visual]);
|
|
59
|
+
if (identity !== v.identity) {
|
|
60
|
+
if (v.visual) {
|
|
61
|
+
primitives.dispose(v.visual);
|
|
62
|
+
v.visual.remove();
|
|
63
|
+
}
|
|
64
|
+
v.visual = primitives.visual(row.visual);
|
|
65
|
+
v.visualStyle = v.visual ? new RowVisualStyle(v.visual) : undefined;
|
|
66
|
+
v.identity = identity;
|
|
67
|
+
}
|
|
68
|
+
if (v.visual) body.insertBefore(v.visual, body.firstChild);
|
|
69
|
+
v.visualStyle?.apply(row.style?.image, r.width, r.height, r.leadingGap - 6);
|
|
70
|
+
}
|
|
71
|
+
function create(document: Document, row: RailRow, primitives: RowPrimitives) {
|
|
72
|
+
const span = (className: string, slot: string) => {
|
|
73
|
+
const view = document.createElement('span');
|
|
74
|
+
view.className = className;
|
|
75
|
+
view.dataset.nlSlot = slot;
|
|
76
|
+
return view;
|
|
77
|
+
};
|
|
78
|
+
const body = document.createElement('div');
|
|
79
|
+
body.className = 'ok-native-list-row ok-native-list-rail';
|
|
80
|
+
body.dataset.nlRenderer = 'rail';
|
|
81
|
+
views.set(body, {
|
|
82
|
+
title: span('ok-native-list-rail-title', 'title'),
|
|
83
|
+
badge: span('ok-native-list-badge', 'badge'),
|
|
84
|
+
status: span('ok-native-list-secondary', 'status'),
|
|
85
|
+
identity: '',
|
|
86
|
+
primitives,
|
|
87
|
+
});
|
|
88
|
+
bind(body, row, primitives);
|
|
89
|
+
return body;
|
|
90
|
+
}
|
|
91
|
+
function recycle(body: HTMLElement) {
|
|
92
|
+
const v = views.get(body);
|
|
93
|
+
if (!v) return;
|
|
94
|
+
if (v.visual) {
|
|
95
|
+
v.primitives.dispose(v.visual);
|
|
96
|
+
v.visual.remove();
|
|
97
|
+
}
|
|
98
|
+
v.visual = undefined;
|
|
99
|
+
v.visualStyle = undefined;
|
|
100
|
+
v.identity = '';
|
|
101
|
+
}
|
|
102
|
+
export function measureRailWidth(row: RailRow) {
|
|
103
|
+
const r = resolveRailRow(row);
|
|
104
|
+
const width = (
|
|
105
|
+
value: string,
|
|
106
|
+
style: NativeListTextStyle | undefined,
|
|
107
|
+
size: number
|
|
108
|
+
) => value.length * 7 * ((style?.fontSize ?? size) / size);
|
|
109
|
+
// Retain the historical 50px allowance and viewport limits at default values.
|
|
110
|
+
return Math.min(
|
|
111
|
+
288,
|
|
112
|
+
Math.max(
|
|
113
|
+
72,
|
|
114
|
+
50 +
|
|
115
|
+
width(r.title, row.style?.title, 12) +
|
|
116
|
+
width(r.badge, row.style?.badge, 11) +
|
|
117
|
+
width(r.status, row.style?.status, 13) +
|
|
118
|
+
(r.horizontalPadding - 4) * 2 +
|
|
119
|
+
r.width -
|
|
120
|
+
20 +
|
|
121
|
+
r.leadingGap -
|
|
122
|
+
6 +
|
|
123
|
+
(r.badge ? r.badgeGap - 6 : 0) +
|
|
124
|
+
(r.status ? r.trailingGap - 6 : 0)
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
export const railRowRenderer = {
|
|
129
|
+
key: 'rail' as const,
|
|
130
|
+
create,
|
|
131
|
+
bind,
|
|
132
|
+
recycle,
|
|
133
|
+
measure: (_row: RailRow, _width: number) => 40,
|
|
134
|
+
measureRendered: (_body: HTMLElement, _row: RailRow): number | undefined =>
|
|
135
|
+
undefined,
|
|
136
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { RowModel } from '../../models';
|
|
2
|
+
|
|
3
|
+
type Write = {
|
|
4
|
+
element: HTMLElement;
|
|
5
|
+
property: string;
|
|
6
|
+
previous: string;
|
|
7
|
+
previousPriority: string;
|
|
8
|
+
written: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// Inline properties written by the container pass, per row body. Renderers only
|
|
12
|
+
// reset the nodes they own, so the container undoes its own writes on rebind.
|
|
13
|
+
const writesByBody = new WeakMap<HTMLElement, Write[]>();
|
|
14
|
+
|
|
15
|
+
function restoreContainerWrites(body: HTMLElement, force: boolean) {
|
|
16
|
+
body.removeAttribute('data-nl-container-background');
|
|
17
|
+
body.removeAttribute('data-nl-container-border');
|
|
18
|
+
const writes = writesByBody.get(body);
|
|
19
|
+
if (!writes) return;
|
|
20
|
+
writesByBody.delete(body);
|
|
21
|
+
// Undo in reverse so repeated writes restore the oldest value.
|
|
22
|
+
for (let index = writes.length - 1; index >= 0; index -= 1) {
|
|
23
|
+
const write = writes[index]!;
|
|
24
|
+
const { style } = write.element;
|
|
25
|
+
// Fallback path only (no reset before the renderer bind): a renderer that
|
|
26
|
+
// already rewrote this property owns its current value. It cannot tell a
|
|
27
|
+
// renderer rewrite of the same value apart, which is why the engine and
|
|
28
|
+
// WalletGroup reset before binding instead.
|
|
29
|
+
if (!force && style.getPropertyValue(write.property) !== write.written)
|
|
30
|
+
continue;
|
|
31
|
+
style.removeProperty(write.property);
|
|
32
|
+
if (write.previous)
|
|
33
|
+
style.setProperty(write.property, write.previous, write.previousPriority);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Undo the previous container pass before a renderer rebinds `body`, so the
|
|
39
|
+
* renderer always writes over its own baseline and the next
|
|
40
|
+
* `applyRowContainerStyle` records that fresh value as the value to restore.
|
|
41
|
+
*/
|
|
42
|
+
export function resetRowContainerStyle(body: HTMLElement): void {
|
|
43
|
+
restoreContainerWrites(body, true);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeProperty(
|
|
47
|
+
body: HTMLElement,
|
|
48
|
+
element: HTMLElement,
|
|
49
|
+
property: string,
|
|
50
|
+
value: string,
|
|
51
|
+
priority = ''
|
|
52
|
+
) {
|
|
53
|
+
const { style } = element;
|
|
54
|
+
const write: Write = {
|
|
55
|
+
element,
|
|
56
|
+
property,
|
|
57
|
+
previous: style.getPropertyValue(property),
|
|
58
|
+
previousPriority: style.getPropertyPriority(property),
|
|
59
|
+
written: '',
|
|
60
|
+
};
|
|
61
|
+
style.setProperty(property, value, priority);
|
|
62
|
+
write.written = style.getPropertyValue(property);
|
|
63
|
+
let writes = writesByBody.get(body);
|
|
64
|
+
if (!writes) writesByBody.set(body, (writes = []));
|
|
65
|
+
writes.push(write);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function applyRowContainerStyle(body: HTMLElement, row: RowModel): void {
|
|
69
|
+
restoreContainerWrites(body, false);
|
|
70
|
+
const set = (element: HTMLElement, property: string, value: string) =>
|
|
71
|
+
writeProperty(body, element, property, value);
|
|
72
|
+
if (
|
|
73
|
+
row.type === 'system' &&
|
|
74
|
+
row.variant === 'warning' &&
|
|
75
|
+
(row.style?.container?.height ?? row.height) !== undefined
|
|
76
|
+
)
|
|
77
|
+
set(body, 'height', '100%');
|
|
78
|
+
const container = row.style?.container;
|
|
79
|
+
if (!container) return;
|
|
80
|
+
if (container.backgroundColor !== undefined) {
|
|
81
|
+
set(body, '--nl-container-background', container.backgroundColor);
|
|
82
|
+
body.setAttribute('data-nl-container-background', 'true');
|
|
83
|
+
// A CSS rule allows existing hover/press feedback to override the resting fill.
|
|
84
|
+
// The template/engine re-applies its own fill on every bind, so this
|
|
85
|
+
// removal is not tracked for restoration.
|
|
86
|
+
body.style.removeProperty('background');
|
|
87
|
+
body.style.removeProperty('background-color');
|
|
88
|
+
}
|
|
89
|
+
if (container.cornerRadius !== undefined)
|
|
90
|
+
set(body, 'border-radius', `${container.cornerRadius}px`);
|
|
91
|
+
if (container.borderWidth !== undefined) {
|
|
92
|
+
// Paint above member backgrounds without changing the content box or hit targets.
|
|
93
|
+
body.setAttribute('data-nl-container-border', 'true');
|
|
94
|
+
set(body, '--nl-container-border-width', `${container.borderWidth}px`);
|
|
95
|
+
set(
|
|
96
|
+
body,
|
|
97
|
+
'--nl-container-border-color',
|
|
98
|
+
container.borderColor ?? 'transparent'
|
|
99
|
+
);
|
|
100
|
+
set(body, 'border-color', 'transparent');
|
|
101
|
+
} else if (container.borderColor !== undefined)
|
|
102
|
+
set(body, 'border-color', container.borderColor);
|
|
103
|
+
if (container.contentVerticalAlignment !== undefined) {
|
|
104
|
+
const alignment = {
|
|
105
|
+
top: 'flex-start',
|
|
106
|
+
center: 'center',
|
|
107
|
+
bottom: 'flex-end',
|
|
108
|
+
}[container.contentVerticalAlignment];
|
|
109
|
+
const vertical =
|
|
110
|
+
row.type === 'walletGroup' ||
|
|
111
|
+
row.type === 'mediaTile' ||
|
|
112
|
+
row.type === 'metricCard' ||
|
|
113
|
+
(row.type === 'identity' && row.presentation === 'walletSidebar') ||
|
|
114
|
+
(row.type === 'system' && row.variant === 'warning');
|
|
115
|
+
if (vertical) {
|
|
116
|
+
set(body, 'display', 'flex');
|
|
117
|
+
set(body, 'flex-direction', 'column');
|
|
118
|
+
if (row.type === 'mediaTile') set(body, 'gap', '0px');
|
|
119
|
+
set(body, 'justify-content', alignment);
|
|
120
|
+
Array.from(body.children).forEach((child) => {
|
|
121
|
+
set(child as HTMLElement, 'flex-shrink', '0');
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
set(body, 'align-items', alignment);
|
|
125
|
+
// Timestamp/accessory defaults may have their own align-self.
|
|
126
|
+
Array.from(body.children).forEach((child) => {
|
|
127
|
+
set(child as HTMLElement, 'align-self', alignment);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { NativeListActionSource } from '../../models';
|
|
2
|
+
export function createElement(
|
|
3
|
+
document: Document,
|
|
4
|
+
tag: string,
|
|
5
|
+
className?: string,
|
|
6
|
+
text?: string
|
|
7
|
+
): HTMLElement {
|
|
8
|
+
const element = document.createElement(tag);
|
|
9
|
+
if (className) element.className = className;
|
|
10
|
+
if (text !== undefined) element.textContent = text;
|
|
11
|
+
return element;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setData(
|
|
15
|
+
element: HTMLElement,
|
|
16
|
+
name: string,
|
|
17
|
+
value: string | number | boolean | undefined
|
|
18
|
+
) {
|
|
19
|
+
if (value === undefined) delete element.dataset[name];
|
|
20
|
+
else element.dataset[name] = String(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function tagSlot<T extends HTMLElement>(element: T, slot: string): T {
|
|
24
|
+
element.dataset.nlSlot = slot;
|
|
25
|
+
return element;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function markActionAnchorSource(
|
|
29
|
+
element: HTMLElement,
|
|
30
|
+
source: NativeListActionSource,
|
|
31
|
+
slot?: number
|
|
32
|
+
) {
|
|
33
|
+
setData(element, 'nativeListAnchorSource', source);
|
|
34
|
+
if (slot !== undefined) setData(element, 'nativeListAnchorSlot', slot);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createTextColumn(
|
|
38
|
+
document: Document,
|
|
39
|
+
title: string,
|
|
40
|
+
subtitle?: string,
|
|
41
|
+
tertiary?: string,
|
|
42
|
+
tertiaryTone?: 'secondary' | 'info',
|
|
43
|
+
badges?: readonly Readonly<{ text: string; tone?: string }>[],
|
|
44
|
+
slots: Readonly<{ title: string; subtitle: string; tertiary: string }> = {
|
|
45
|
+
title: 'title',
|
|
46
|
+
subtitle: 'subtitle',
|
|
47
|
+
tertiary: 'tertiary',
|
|
48
|
+
}
|
|
49
|
+
): HTMLElement {
|
|
50
|
+
const column = createElement(document, 'span', 'ok-native-list-flex');
|
|
51
|
+
const titleLine = tagSlot(
|
|
52
|
+
createElement(document, 'span', 'ok-native-list-title', title),
|
|
53
|
+
slots.title
|
|
54
|
+
);
|
|
55
|
+
if (badges?.length) {
|
|
56
|
+
titleLine.removeAttribute('data-nl-slot');
|
|
57
|
+
titleLine.replaceChildren(
|
|
58
|
+
tagSlot(createElement(document, 'span', undefined, title), slots.title)
|
|
59
|
+
);
|
|
60
|
+
const badgeLine = createElement(document, 'span', 'ok-native-list-badges');
|
|
61
|
+
badges.forEach((badge) =>
|
|
62
|
+
badgeLine.appendChild(createBadge(document, badge))
|
|
63
|
+
);
|
|
64
|
+
titleLine.appendChild(badgeLine);
|
|
65
|
+
}
|
|
66
|
+
column.appendChild(titleLine);
|
|
67
|
+
if (subtitle)
|
|
68
|
+
column.appendChild(
|
|
69
|
+
tagSlot(
|
|
70
|
+
createElement(document, 'span', 'ok-native-list-secondary', subtitle),
|
|
71
|
+
slots.subtitle
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
if (tertiary) {
|
|
75
|
+
const element = tagSlot(
|
|
76
|
+
createElement(
|
|
77
|
+
document,
|
|
78
|
+
'span',
|
|
79
|
+
tertiaryTone === 'info'
|
|
80
|
+
? 'ok-native-list-secondary ok-native-list-info'
|
|
81
|
+
: 'ok-native-list-secondary ok-native-list-tertiary',
|
|
82
|
+
tertiary
|
|
83
|
+
),
|
|
84
|
+
slots.tertiary
|
|
85
|
+
);
|
|
86
|
+
column.appendChild(element);
|
|
87
|
+
}
|
|
88
|
+
return column;
|
|
89
|
+
}
|
|
90
|
+
export function createBadge(
|
|
91
|
+
document: Document,
|
|
92
|
+
badge: Readonly<{ text: string; tone?: string }>
|
|
93
|
+
): HTMLElement {
|
|
94
|
+
const element = tagSlot(
|
|
95
|
+
createElement(document, 'span', 'ok-native-list-badge', badge.text),
|
|
96
|
+
'badge'
|
|
97
|
+
);
|
|
98
|
+
setData(element, 'tone', badge.tone);
|
|
99
|
+
return element;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Selector digits retain their font family, including explicit button fonts.
|
|
103
|
+
export function applyTabularNumbers(body: HTMLElement): void {
|
|
104
|
+
body.style.fontVariantNumeric = 'tabular-nums';
|
|
105
|
+
body.querySelectorAll<HTMLElement>('span,button').forEach((text) => {
|
|
106
|
+
text.style.fontVariantNumeric = 'tabular-nums';
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Selector presentations use their explicit-height geometry whenever the row
|
|
112
|
+
* has a resolved explicit height: the legacy `row.height` or the preferred
|
|
113
|
+
* `style.container.height`.
|
|
114
|
+
*/
|
|
115
|
+
export function hasExplicitRowHeight(
|
|
116
|
+
row: Readonly<{
|
|
117
|
+
height?: number;
|
|
118
|
+
style?: Readonly<{ container?: Readonly<{ height?: number }> }>;
|
|
119
|
+
}>
|
|
120
|
+
): boolean {
|
|
121
|
+
return (row.style?.container?.height ?? row.height) !== undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Legacy `size` preset adjustment, applied by templates that opt in. */
|
|
125
|
+
export function rowSizeModifier(size: string | undefined): number {
|
|
126
|
+
if (size === 'small') return -8;
|
|
127
|
+
if (size === 'large') return 12;
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Async image reveal state is owned by the image lifecycle, not by template
|
|
132
|
+
// defaults: a reused image that already fired `load` never fires it again.
|
|
133
|
+
const isImageLoadingNode = (node: HTMLElement) =>
|
|
134
|
+
node.tagName === 'IMG' ||
|
|
135
|
+
node.classList.contains('ok-native-list-selector-image-background');
|
|
136
|
+
|
|
137
|
+
/** Captures factory inline styles of a template subtree. */
|
|
138
|
+
export function captureInlineStyles(
|
|
139
|
+
root: HTMLElement
|
|
140
|
+
): Map<HTMLElement, string> {
|
|
141
|
+
return new Map(
|
|
142
|
+
[root, ...root.querySelectorAll<HTMLElement>('*')].map((node) => [
|
|
143
|
+
node,
|
|
144
|
+
node.style.cssText,
|
|
145
|
+
])
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Restores captured factory styles while keeping image loading visibility. */
|
|
150
|
+
export function restoreInlineStyles(defaults: Map<HTMLElement, string>) {
|
|
151
|
+
defaults.forEach((css, node) => {
|
|
152
|
+
if (!isImageLoadingNode(node)) {
|
|
153
|
+
node.style.cssText = css;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const opacity = node.style.opacity;
|
|
157
|
+
node.style.cssText = css;
|
|
158
|
+
node.style.opacity = opacity;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { WalletGroupRow } from '../../models';
|
|
2
|
+
import { walletGroupRowRenderer } from './WalletGroupRowRenderer';
|
|
3
|
+
import type { IdentityRow, SectionHeaderRow } from '../../models';
|
|
4
|
+
import { identityRowRenderer } from './IdentityRowRenderer';
|
|
5
|
+
import { sectionHeaderRowRenderer } from './SectionHeaderRowRenderer';
|
|
6
|
+
import type { MarketRow } from '../../models';
|
|
7
|
+
import { marketRowRenderer } from './MarketRowRenderer';
|
|
8
|
+
import type { MetricCardRow } from '../../models';
|
|
9
|
+
import { metricCardRowRenderer } from './MetricCardRowRenderer';
|
|
10
|
+
import type { DataRow } from '../../models';
|
|
11
|
+
import { dataRowRenderer } from './DataRowRenderer';
|
|
12
|
+
import type { ActivityRow } from '../../models';
|
|
13
|
+
import { activityRowRenderer } from './ActivityRowRenderer';
|
|
14
|
+
import type { SystemRow } from '../../models';
|
|
15
|
+
import { systemRowRenderer } from './SystemRowRenderer';
|
|
16
|
+
import type { ActionRow } from '../../models';
|
|
17
|
+
import { actionRowRenderer } from './ActionRowRenderer';
|
|
18
|
+
import { mediaTileRowRenderer } from './MediaTileRowRenderer';
|
|
19
|
+
import type { MessageRow, RailRow, MediaTileRow, RowModel } from '../../models';
|
|
20
|
+
import { messageRowRenderer } from './MessageRowRenderer';
|
|
21
|
+
import type { RowPrimitives } from './RowVisual';
|
|
22
|
+
import { railRowRenderer } from './RailRowRenderer';
|
|
23
|
+
|
|
24
|
+
type Renderer<R extends RowModel> = {
|
|
25
|
+
key: string;
|
|
26
|
+
appliesSizePreset?: (row: R) => boolean;
|
|
27
|
+
create: (
|
|
28
|
+
document: Document,
|
|
29
|
+
row: R,
|
|
30
|
+
primitives: RowPrimitives
|
|
31
|
+
) => HTMLElement;
|
|
32
|
+
bind: (body: HTMLElement, row: R, primitives: RowPrimitives) => void;
|
|
33
|
+
measure: (row: R, width: number, layout?: string) => number;
|
|
34
|
+
measureRendered: (body: HTMLElement, row: R) => number | undefined;
|
|
35
|
+
recycle: (body: HTMLElement) => void;
|
|
36
|
+
reorderPreview?: (
|
|
37
|
+
body: HTMLElement,
|
|
38
|
+
row: R
|
|
39
|
+
) => { element: HTMLElement; height: number } | undefined;
|
|
40
|
+
};
|
|
41
|
+
function registration<R extends RowModel>(renderer: Renderer<R>, row: R) {
|
|
42
|
+
return {
|
|
43
|
+
key: renderer.key,
|
|
44
|
+
appliesSizePreset: renderer.appliesSizePreset?.(row) ?? true,
|
|
45
|
+
create: (document: Document, primitives: RowPrimitives) =>
|
|
46
|
+
renderer.create(document, row, primitives),
|
|
47
|
+
bind: (body: HTMLElement, primitives: RowPrimitives) =>
|
|
48
|
+
renderer.bind(body, row, primitives),
|
|
49
|
+
measure: (width: number, layout?: string) =>
|
|
50
|
+
renderer.measure(row, width, layout),
|
|
51
|
+
measureRendered: (body: HTMLElement) => renderer.measureRendered(body, row),
|
|
52
|
+
reorderPreview: (body: HTMLElement) => renderer.reorderPreview?.(body, row),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const factories = {
|
|
56
|
+
walletGroup: (row: RowModel) =>
|
|
57
|
+
registration(walletGroupRowRenderer, row as WalletGroupRow),
|
|
58
|
+
identity: (row: RowModel) =>
|
|
59
|
+
registration(identityRowRenderer, row as IdentityRow),
|
|
60
|
+
sectionHeader: (row: RowModel) =>
|
|
61
|
+
registration(sectionHeaderRowRenderer, row as SectionHeaderRow),
|
|
62
|
+
market: (row: RowModel) => registration(marketRowRenderer, row as MarketRow),
|
|
63
|
+
metricCard: (row: RowModel) =>
|
|
64
|
+
registration(metricCardRowRenderer, row as MetricCardRow),
|
|
65
|
+
dataRow: (row: RowModel) => registration(dataRowRenderer, row as DataRow),
|
|
66
|
+
activity: (row: RowModel) =>
|
|
67
|
+
registration(activityRowRenderer, row as ActivityRow),
|
|
68
|
+
system: (row: RowModel) => registration(systemRowRenderer, row as SystemRow),
|
|
69
|
+
action: (row: RowModel) => registration(actionRowRenderer, row as ActionRow),
|
|
70
|
+
message: (row: RowModel) =>
|
|
71
|
+
registration(messageRowRenderer, row as MessageRow),
|
|
72
|
+
rail: (row: RowModel) => registration(railRowRenderer, row as RailRow),
|
|
73
|
+
mediaTile: (row: RowModel) =>
|
|
74
|
+
registration(mediaTileRowRenderer, row as MediaTileRow),
|
|
75
|
+
} satisfies Record<
|
|
76
|
+
RowModel['type'],
|
|
77
|
+
(row: RowModel) => ReturnType<typeof registration>
|
|
78
|
+
>;
|
|
79
|
+
const renderers = {
|
|
80
|
+
walletGroup: walletGroupRowRenderer,
|
|
81
|
+
identity: identityRowRenderer,
|
|
82
|
+
sectionHeader: sectionHeaderRowRenderer,
|
|
83
|
+
market: marketRowRenderer,
|
|
84
|
+
metricCard: metricCardRowRenderer,
|
|
85
|
+
dataRow: dataRowRenderer,
|
|
86
|
+
activity: activityRowRenderer,
|
|
87
|
+
system: systemRowRenderer,
|
|
88
|
+
action: actionRowRenderer,
|
|
89
|
+
message: messageRowRenderer,
|
|
90
|
+
rail: railRowRenderer,
|
|
91
|
+
mediaTile: mediaTileRowRenderer,
|
|
92
|
+
};
|
|
93
|
+
export type RowRendererKey = RowModel['type'];
|
|
94
|
+
const isRegistered = (key: string): key is keyof typeof factories =>
|
|
95
|
+
Object.prototype.hasOwnProperty.call(factories, key);
|
|
96
|
+
|
|
97
|
+
// Closed registration; a row's data key, styles and placement never affect reuse.
|
|
98
|
+
export function rowRenderer(row: RowModel) {
|
|
99
|
+
if (!isRegistered(row.type))
|
|
100
|
+
throw new Error('No renderer registered for ' + row.type);
|
|
101
|
+
return factories[row.type](row);
|
|
102
|
+
}
|
|
103
|
+
export function rowRendererKey(row: RowModel): RowRendererKey {
|
|
104
|
+
return row.type;
|
|
105
|
+
}
|
|
106
|
+
export function recycleRowBody(body: HTMLElement): boolean {
|
|
107
|
+
const key = body.dataset.nlRenderer ?? '';
|
|
108
|
+
if (!isRegistered(key)) return false;
|
|
109
|
+
renderers[key].recycle(body);
|
|
110
|
+
return true;
|
|
111
|
+
}
|