@onekeyfe/react-native-native-list 3.0.112 → 3.0.113

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.
@@ -3,6 +3,8 @@ import type {
3
3
  CheckboxState,
4
4
  ImageSource,
5
5
  LeadingVisual,
6
+ MarketRow,
7
+ MarketTextStyle,
6
8
  NativeListSnapshot,
7
9
  NativeListActionAnchor,
8
10
  NativeListActionSource,
@@ -60,6 +62,10 @@ export const WEB_REORDER_ANIMATION = {
60
62
  } as const;
61
63
 
62
64
  const REORDER_TOUCH_LONG_PRESS_MS = 120;
65
+ const MARKET_LONG_PRESS_MS = 800;
66
+ const MARKET_MOVE_CANCEL_PX = 10;
67
+ export const WEB_MARKET_VERIFIED_PATH =
68
+ 'M9.483 11.458v3.5h-1v-3.5z M10.467 2.698a2.03 2.03 0 0 1 3.065 0l1.358 1.564a.03.03 0 0 0 .028.01l2.046-.325a2.03 2.03 0 0 1 2.347 1.971l.037 2.07q0 .016.014.026l1.776 1.066a2.03 2.03 0 0 1 .532 3.019l-1.304 1.609a.03.03 0 0 0-.005.03l.675 1.956a2.03 2.03 0 0 1-1.533 2.656l-2.033.394a.03.03 0 0 0-.023.019l-.741 1.933a2.03 2.03 0 0 1-2.88 1.05l-1.811-1.006a.03.03 0 0 0-.03 0l-1.811 1.005a2.03 2.03 0 0 1-2.88-1.049l-.742-1.933a.03.03 0 0 0-.023-.019l-2.033-.394a2.03 2.03 0 0 1-1.532-2.656l.675-1.957a.03.03 0 0 0-.005-.029l-1.304-1.61a2.03 2.03 0 0 1 .532-3.018l1.776-1.066a.03.03 0 0 0 .014-.026l.035-2.07a2.03 2.03 0 0 1 2.349-1.97l2.045.324a.03.03 0 0 0 .028-.01zm1.516 3.76a.5.5 0 0 0-.447.276l-1.861 3.724H8.483a1 1 0 0 0-1 1v3.5a1 1 0 0 0 1 1h6.692a2 2 0 0 0 1.981-1.73l.341-2.5a2 2 0 0 0-1.982-2.27h-1.939l.197-1.269a1.5 1.5 0 0 0-1.481-1.731z';
63
69
  const REORDER_MOUSE_MOVE_THRESHOLD_PX = 5;
64
70
  const REORDER_EDGE_RATIO = 0.25;
65
71
  const REORDER_MAX_SPEED_ZONE_RATIO = 0.05;
@@ -108,6 +114,50 @@ export type WebListLayout = Readonly<{
108
114
  horizontal: boolean;
109
115
  }>;
110
116
 
117
+ export type WebMarketLayoutStyle = Readonly<{
118
+ horizontalPadding: number;
119
+ verticalPadding: number;
120
+ leadingGap: number;
121
+ titleBadgeGap: number;
122
+ trailingGap: number;
123
+ imageWidth: number;
124
+ imageHeight: number;
125
+ imageCornerRadius: number;
126
+ changeWidth: number;
127
+ changeHeight: number;
128
+ changeCornerRadius: number;
129
+ }>;
130
+
131
+ /** Resolves every reusable Market geometry field, including legacy defaults. */
132
+ export function resolveWebMarketLayoutStyle(
133
+ row: MarketRow
134
+ ): WebMarketLayoutStyle {
135
+ const style = row.style;
136
+ const imageWidth = style?.image?.width ?? (row.variant === 'stock' ? 40 : 32);
137
+ const imageHeight =
138
+ style?.image?.height ?? (row.variant === 'stock' ? 40 : 32);
139
+ return {
140
+ horizontalPadding:
141
+ style?.horizontalPadding ?? (row.variant === 'perp' ? 16 : 20),
142
+ verticalPadding: style?.verticalPadding ?? 12,
143
+ leadingGap: style?.leadingGap ?? (row.variant === 'perp' ? 8 : 14),
144
+ titleBadgeGap: style?.titleBadgeGap ?? 4,
145
+ trailingGap: style?.trailingGap ?? 8,
146
+ imageWidth,
147
+ imageHeight,
148
+ imageCornerRadius:
149
+ style?.image?.cornerRadius ??
150
+ (style?.image?.shape === 'square'
151
+ ? 0
152
+ : style?.image?.shape === 'rounded'
153
+ ? 8
154
+ : Math.min(imageWidth, imageHeight) / 2),
155
+ changeWidth: style?.changeWidth ?? 80,
156
+ changeHeight: style?.changeHeight ?? 32,
157
+ changeCornerRadius: style?.changeCornerRadius ?? 8,
158
+ };
159
+ }
160
+
111
161
  export type NativeListWebCallbacks = Readonly<{
112
162
  onRowAction?: (event: RowActionEvent) => void;
113
163
  onActionAnchorInvalidated?: (event: ActionAnchorInvalidatedEvent) => void;
@@ -450,7 +500,15 @@ export function estimateWebRowHeight(
450
500
  }
451
501
  case 'system':
452
502
  base =
453
- row.variant === 'noMatch' || row.variant === 'end'
503
+ row.variant === 'loading' && row.loadingStyle === 'skeleton'
504
+ ? 56
505
+ : row.variant === 'loading' && row.loadingStyle === 'spinner'
506
+ ? 52
507
+ : 'presentation' in row && row.presentation === 'market'
508
+ ? row.variant === 'loading'
509
+ ? 68
510
+ : 44
511
+ : row.variant === 'noMatch' || row.variant === 'end'
454
512
  ? 36
455
513
  : row.variant === 'retry'
456
514
  ? 44
@@ -462,6 +520,14 @@ export function estimateWebRowHeight(
462
520
  case 'dataRow':
463
521
  base = row.columns.some((column) => column.secondaryText) ? 60 : 56;
464
522
  break;
523
+ case 'market': {
524
+ const marketStyle = resolveWebMarketLayoutStyle(row);
525
+ base = Math.max(
526
+ row.variant === 'stock' ? 72 : 68,
527
+ marketStyle.imageHeight + marketStyle.verticalPadding * 2
528
+ );
529
+ break;
530
+ }
465
531
  case 'identity':
466
532
  base = row.tertiary ? 72 : row.subtitle ? 60 : 56;
467
533
  break;
@@ -742,6 +808,30 @@ export function webRowRenderSignature(row: RowModel): string {
742
808
  return JSON.stringify(rowWithoutSelectionState(row));
743
809
  }
744
810
 
811
+ const MARKET_QUOTE_PATCH_FIELDS = new Set([
812
+ 'revision',
813
+ 'price',
814
+ 'priceSegments',
815
+ 'change',
816
+ 'accessibilityLabel',
817
+ ]);
818
+
819
+ export function isWebMarketQuotePatch(patch: RowPatch): boolean {
820
+ return (
821
+ patch.type === 'market' &&
822
+ Object.keys(patch.changes).every((key) =>
823
+ MARKET_QUOTE_PATCH_FIELDS.has(key)
824
+ )
825
+ );
826
+ }
827
+
828
+ /** Number of Market image-binding passes caused by one Web patch transaction. */
829
+ export function webMarketImageBindDeltaForPatches(
830
+ patches: readonly RowPatch[]
831
+ ): 0 | 1 {
832
+ return patches.length > 0 && patches.every(isWebMarketQuotePatch) ? 0 : 1;
833
+ }
834
+
745
835
  // OneKey patch: selector names must shrink before the sidebar clips their contents.
746
836
  export const WEB_LIST_CSS = `
747
837
  [data-native-list-selector="walletSidebar"] .ok-native-list-title{max-width:100%;min-width:0}
@@ -798,11 +888,14 @@ export const WEB_LIST_CSS = `
798
888
  .ok-native-list-media{display:block;padding:0 5px;background:transparent;border-radius:16px}.ok-native-list-media-image{display:block;width:100%;aspect-ratio:1;border-radius:10px;background:var(--nl-strong);object-fit:cover}.ok-native-list-media-image[data-state="empty"]{background:transparent}.ok-native-list-media-image[data-state="error"]{display:flex;align-items:center;justify-content:center;color:var(--nl-icon-subdued);font-size:24px}.ok-native-list-media-meta{padding-top:7px}.ok-native-list-media-subtitle-row{display:flex;align-items:center;gap:6px}.ok-native-list-media-subtitle{flex:1;min-width:0;font-size:12px;color:var(--nl-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-media-network{width:14px;height:14px;border-radius:50%}.ok-native-list-media-title{font-size:16px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ok-native-list-media-close{position:absolute;right:9px;top:4px;border:0;background:color-mix(in srgb,var(--nl-inverse) 72%,transparent);color:var(--nl-inverse-text);width:24px;height:24px;border-radius:50%;font:18px/20px inherit;cursor:pointer}
799
889
  .ok-native-list-metric{display:flex;flex-direction:column;align-items:flex-start;padding:12px;border-radius:12px;gap:5px;background:var(--nl-row)}.ok-native-list-metric-value{font-size:22px;line-height:28px;font-weight:700}.ok-native-list-composite{display:flex;flex-direction:column;align-items:stretch;padding:14px;border-radius:12px;gap:12px;background:var(--nl-subdued)}.ok-native-list-composite-heading{font-size:14px;letter-spacing:1px;color:var(--nl-secondary)}.ok-native-list-composite-row{display:flex;gap:12px}.ok-native-list-composite-cell{flex:1;min-width:0}.ok-native-list-composite-cell[data-shaded="true"]{padding:10px;border-radius:10px;background:color-mix(in srgb,var(--nl-primary) 5%,transparent)}.ok-native-list-composite-value{font-size:18px;font-weight:600}.ok-native-list-divider{height:1px;background:var(--nl-separator)}.ok-native-list-progress{height:4px;border-radius:2px;overflow:hidden;background:var(--nl-negative)}.ok-native-list-progress>span{display:block;height:100%;border-radius:2px;background:var(--nl-positive)}
800
890
  .ok-native-list-data{padding:6px 12px}.ok-native-list-index{flex:0 0 28px;color:var(--nl-secondary);font-size:13px}.ok-native-list-favorite{flex:0 0 24px;color:var(--nl-icon-subdued);font-size:22px}.ok-native-list-favorite[data-active="true"]{color:var(--nl-accent)}.ok-native-list-data-cell{display:flex;flex-direction:column;min-width:0}.ok-native-list-data-cell[data-align="center"]{align-items:center}.ok-native-list-data-cell[data-align="end"]{align-items:flex-end}.ok-native-list-data-primary{display:flex;align-items:center;gap:5px;max-width:100%;font-size:16px;font-weight:500;white-space:nowrap}.ok-native-list-unread{width:7px;height:7px;flex:0 0 7px;border-radius:50%;background:var(--nl-accent)}.ok-native-list-thumbnail{width:64px;height:64px;border-radius:10px;object-fit:cover}
891
+ .ok-native-list-market{padding:12px 20px;gap:14px}.ok-native-list-market>.ok-native-list-visual{width:32px;height:32px;flex-basis:32px}.ok-native-list-market[data-variant="stock"]>.ok-native-list-visual{width:40px;height:40px;flex-basis:40px}.ok-native-list-market>.ok-native-list-visual>.ok-native-list-visual-main{width:100%;height:100%}.ok-native-list-market-main{display:flex;flex:1;min-width:0;flex-direction:column;justify-content:center}.ok-native-list-market-title-line{display:flex;align-items:center;min-width:0;gap:4px}.ok-native-list-market-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nl-primary);font-size:16px;line-height:24px;font-weight:500}.ok-native-list-market-subtitle{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--nl-secondary);font-size:14px;line-height:20px;font-weight:400}.ok-native-list-market-badge{display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;box-sizing:border-box;max-width:72px;height:18px;padding:0 5px;border:0;border-radius:4px;background:var(--nl-strong);color:var(--nl-secondary);font:500 11px/18px inherit;overflow:hidden;white-space:nowrap}.ok-native-list-market-badge img{width:14px;height:14px;object-fit:contain}.ok-native-list-market-trailing{display:flex;flex:0 0 auto;align-items:center;gap:8px}.ok-native-list-market-price{max-width:112px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right;color:var(--nl-primary);font-size:16px;line-height:24px;font-weight:500;font-variant-numeric:tabular-nums}.ok-native-list-market-change{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:80px;height:32px;border-radius:8px;color:#fff;background:#8d8d8d;font-size:14px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap}.ok-native-list-market-change[data-tone="positive"]{background:var(--nl-positive)}.ok-native-list-market-change[data-tone="negative"]{background:var(--nl-negative)}
892
+ .ok-native-list-market-badge>svg,.ok-native-list-market-badge>.ok-native-list-market-badge-icon>svg{display:block;width:16px;height:16px;flex:0 0 16px}.ok-native-list-system[data-native-list-presentation="market"]{box-sizing:border-box;padding:12px 20px}.ok-native-list-system[data-native-list-presentation="market"]>.ok-native-list-spinner{width:32px;height:32px}
801
893
  .ok-native-list-footer{flex:0 0 auto;min-height:0}.ok-native-list-sticky{position:absolute;z-index:4;left:0;right:0;top:0;pointer-events:auto;box-shadow:0 1px 0 var(--nl-separator)}.ok-native-list-index-rail{position:absolute;z-index:6;top:0;right:0;bottom:0;width:${SECTION_INDEX_RAIL_WIDTH}px;touch-action:none;cursor:pointer}.ok-native-list-index-rail[hidden]{display:none}.ok-native-list-index-button{appearance:none;position:absolute;left:6px;display:flex;width:20px;height:16px;align-items:center;justify-content:center;padding:0;transform:translateY(-50%);border:0;border-radius:8px;background:transparent;color:var(--nl-secondary);font:600 10px/1 inherit;cursor:pointer}.ok-native-list-index-button[data-active="true"]{background:var(--nl-accent);color:var(--nl-inverse-text)}.ok-native-list-index-button:focus-visible{outline:2px solid var(--nl-accent);outline-offset:1px}.ok-native-list-index-preview{position:absolute;z-index:8;right:40px;top:50%;display:flex;width:48px;height:48px;align-items:center;justify-content:center;transform:translateY(-50%) scale(.92);border-radius:14px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:22px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .15s ease,transform .15s ease}.ok-native-list-index-preview[data-visible="true"]{opacity:1;transform:translateY(-50%) scale(1)}
802
894
  .ok-native-list-refresh{position:absolute;z-index:7;left:50%;top:8px;display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;background:var(--nl-inverse);color:var(--nl-inverse-text);font-size:12px;opacity:0;transform:translate(-50%,-16px);transition:opacity .15s ease,transform .15s ease;pointer-events:none}.ok-native-list-refresh[data-visible="true"]{opacity:1;transform:translate(-50%,0)}
803
895
  .ok-native-list-warning{height:auto;display:flex;flex-direction:column;align-items:stretch;gap:4px;padding:14px 12px;border-top:1px solid;border-bottom:1px solid;box-sizing:border-box;cursor:default}.ok-native-list-warning-title,.ok-native-list-warning-message{font-size:14px;line-height:20px;white-space:normal;overflow-wrap:anywhere}.ok-native-list-warning-title{font-weight:500;color:var(--nl-primary)}.ok-native-list-warning-message{font-weight:400;color:var(--nl-secondary)}
804
896
  .ok-native-list-subtitle-segments{display:flex;align-items:center;min-width:0;max-width:100%;height:20px}.ok-native-list-subtitle-segments>.ok-native-list-secondary{flex:0 1 auto;min-width:0}.ok-native-list-subtitle-dot{flex:0 0 4px;width:4px;height:4px;margin:0 6px;border-radius:50%;background:var(--nl-disabled)}.ok-native-list-wallet-row>.ok-native-list-flex{flex:0 1 auto;width:100%;align-items:center}.ok-native-list-wallet-badges{display:flex;gap:4px;justify-content:center;margin-top:4px;height:20px;max-width:100%}.ok-native-list-wallet-badges>.ok-native-list-badge{background:var(--nl-strong);color:var(--nl-secondary);font-size:12px;line-height:16px;height:20px;box-sizing:border-box;padding:2px 4px}.ok-native-list-visual-overlay{position:absolute;display:flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:50%;overflow:hidden;line-height:1;font-size:10px}.ok-native-list-visual-overlay img,.ok-native-list-visual-overlay svg{width:100%;height:100%;object-fit:contain}
805
- @media (prefers-reduced-motion:reduce){.ok-native-list-index-preview,.ok-native-list-refresh{transition:none}.ok-native-list-spinner{animation:none}}
897
+ .ok-native-list-row.ok-native-list-market-skeleton{padding:12px 20px;gap:0}.ok-native-list-skeleton-left{display:flex;align-items:center;gap:12px;flex:1}.ok-native-list-skeleton-text{display:flex;flex-direction:column;gap:4px}.ok-native-list-skeleton-right{display:flex;align-items:center;gap:8px}.ok-native-list-skeleton-mark{display:block;flex-shrink:0;border-radius:8px;animation:ok-native-list-skeleton 1.5s linear infinite alternate}@keyframes ok-native-list-skeleton{from{background-color:var(--nl-skeleton-base)}to{background-color:var(--nl-skeleton-highlight)}}.ok-native-list-market-spinner{display:block;width:20px;height:20px;flex-shrink:0;color:var(--nl-icon);animation:ok-native-list-spin .75s linear infinite}
898
+ @media (prefers-reduced-motion:reduce){.ok-native-list-index-preview,.ok-native-list-refresh{transition:none}.ok-native-list-spinner,.ok-native-list-market-spinner{animation:none}.ok-native-list-skeleton-mark{animation:none;background:var(--nl-skeleton-base)}}
806
899
  /* OneKey patch: selector controls follow their original semantic colors and geometry. */
807
900
  .ok-native-list-checkbox[data-selector="networkSelector"]{padding:0;border-radius:4px;border-color:var(--nl-checkbox-border,var(--nl-separator));background:var(--nl-checkbox-icon,var(--nl-inverse-text))}
808
901
  .ok-native-list-checkbox[data-selector="networkSelector"]::after{display:none}
@@ -1280,6 +1373,17 @@ const selectorIcons: Readonly<
1280
1373
  },
1281
1374
  ],
1282
1375
  },
1376
+ BadgeVerifiedSolid: {
1377
+ viewBox: '0 0 24 24',
1378
+ paths: [
1379
+ {
1380
+ d: WEB_MARKET_VERIFIED_PATH,
1381
+ fill: 'currentColor',
1382
+ fillRule: 'evenodd',
1383
+ opacity: 1,
1384
+ },
1385
+ ],
1386
+ },
1283
1387
  };
1284
1388
  function applySelectorIcon(element: HTMLElement, name: string) {
1285
1389
  const icon = selectorIcons[name];
@@ -1335,6 +1439,7 @@ function visualFromRow(row: RowModel): LeadingVisual | undefined {
1335
1439
  if (row.type === 'activity') return row.leading;
1336
1440
  if (row.type === 'message') return row.leading;
1337
1441
  if (row.type === 'dataRow') return row.leading;
1442
+ if (row.type === 'market') return row.leading;
1338
1443
  if (row.type === 'metricCard') return row.visual;
1339
1444
  return undefined;
1340
1445
  }
@@ -2089,6 +2194,98 @@ function createSystemRow(
2089
2194
  'ok-native-list-row ok-native-list-system'
2090
2195
  );
2091
2196
  setData(body, 'variant', row.variant);
2197
+ if ('presentation' in row)
2198
+ setData(body, 'nativeListPresentation', row.presentation);
2199
+ if (row.variant === 'loading' && row.loadingStyle === 'skeleton') {
2200
+ body.classList.add('ok-native-list-market-skeleton');
2201
+ const background = resolvedTheme(context.snapshot).background;
2202
+ const rgb = Number.parseInt(background.slice(1, 7), 16);
2203
+ const dark =
2204
+ ((rgb >> 16) & 255) * 0.299 +
2205
+ ((rgb >> 8) & 255) * 0.587 +
2206
+ (rgb & 255) * 0.114 <
2207
+ 128;
2208
+ body.style.setProperty('--nl-skeleton-base', dark ? '#111111' : '#fafafa');
2209
+ body.style.setProperty(
2210
+ '--nl-skeleton-highlight',
2211
+ dark ? '#333333' : '#cdcdcd'
2212
+ );
2213
+ const left = createElement(
2214
+ context.document,
2215
+ 'div',
2216
+ 'ok-native-list-skeleton-left'
2217
+ );
2218
+ const mark = (width: number, height: number, circle = false) => {
2219
+ const element = createElement(
2220
+ context.document,
2221
+ 'span',
2222
+ 'ok-native-list-skeleton-mark'
2223
+ );
2224
+ element.style.width = `${width}px`;
2225
+ element.style.height = `${height}px`;
2226
+ if (circle) element.style.borderRadius = '50%';
2227
+ return element;
2228
+ };
2229
+ left.appendChild(mark(32, 32, true));
2230
+ const text = createElement(
2231
+ context.document,
2232
+ 'div',
2233
+ 'ok-native-list-skeleton-text'
2234
+ );
2235
+ text.appendChild(mark(80, 16));
2236
+ text.appendChild(mark(60, 12));
2237
+ left.appendChild(text);
2238
+ body.appendChild(left);
2239
+ const right = createElement(
2240
+ context.document,
2241
+ 'div',
2242
+ 'ok-native-list-skeleton-right'
2243
+ );
2244
+ right.appendChild(mark(80, 18));
2245
+ right.appendChild(mark(80, 18));
2246
+ body.appendChild(right);
2247
+ return body;
2248
+ }
2249
+ if (row.variant === 'loading' && row.loadingStyle === 'spinner') {
2250
+ body.style.justifyContent = 'center';
2251
+ body.style.padding = '16px';
2252
+ const spinner = createElement(
2253
+ context.document,
2254
+ 'span',
2255
+ 'ok-native-list-market-spinner'
2256
+ );
2257
+ spinner.setAttribute('role', 'progressbar');
2258
+ // Same 20px SVG and 750ms rotation as react-native-web ActivityIndicator.
2259
+ spinner.innerHTML =
2260
+ '<svg viewBox="0 0 32 32" width="20" height="20"><circle cx="16" cy="16" r="14" fill="none" stroke="currentColor" stroke-width="4" opacity="0.2"/><circle cx="16" cy="16" r="14" fill="none" stroke="currentColor" stroke-width="4" stroke-dasharray="80" stroke-dashoffset="60"/></svg>';
2261
+ body.appendChild(spinner);
2262
+ return body;
2263
+ }
2264
+ if ('presentation' in row && row.presentation === 'market') {
2265
+ if (row.variant === 'noMatch') {
2266
+ body.style.justifyContent = 'center';
2267
+ body.style.padding = '32px';
2268
+ const message = createElement(context.document, 'span', '', row.message);
2269
+ message.style.fontSize = '16px';
2270
+ message.style.lineHeight = '24px';
2271
+ body.appendChild(message);
2272
+ return body;
2273
+ }
2274
+ if (row.variant === 'end') {
2275
+ body.style.justifyContent = 'center';
2276
+ body.style.padding = '16px';
2277
+ body.style.gap = '8px';
2278
+ for (const width of [80, 4, 80]) {
2279
+ const mark = createElement(context.document, 'span', '');
2280
+ mark.style.width = String(width) + 'px';
2281
+ mark.style.height = width === 4 ? '4px' : '1px';
2282
+ mark.style.borderRadius = width === 4 ? '2px' : '0';
2283
+ mark.style.background = 'var(--nl-separator)';
2284
+ body.appendChild(mark);
2285
+ }
2286
+ return body;
2287
+ }
2288
+ }
2092
2289
  if (row.variant === 'warning') {
2093
2290
  body.classList.add('ok-native-list-warning');
2094
2291
  body.style.borderColor = row.borderColor ?? 'var(--nl-separator)';
@@ -2784,6 +2981,266 @@ function createWalletGroupRow(
2784
2981
  return body;
2785
2982
  }
2786
2983
 
2984
+ function marketFontWeight(
2985
+ value: MarketTextStyle['fontWeight'] | undefined,
2986
+ fallback: number
2987
+ ): number {
2988
+ if (value === 'bold') return 700;
2989
+ if (value === 'semibold') return 600;
2990
+ if (value === 'medium') return 500;
2991
+ if (value === 'regular') return 400;
2992
+ return fallback;
2993
+ }
2994
+
2995
+ function applyMarketTextStyle(
2996
+ element: HTMLElement,
2997
+ style: MarketTextStyle | undefined,
2998
+ defaults: Readonly<{
2999
+ fontSize: number;
3000
+ lineHeight: number;
3001
+ weight: number;
3002
+ alignment: 'start' | 'center' | 'end';
3003
+ }>
3004
+ ) {
3005
+ element.style.fontSize = String(style?.fontSize ?? defaults.fontSize) + 'px';
3006
+ element.style.lineHeight =
3007
+ String(style?.lineHeight ?? defaults.lineHeight) + 'px';
3008
+ element.style.fontWeight = String(
3009
+ marketFontWeight(style?.fontWeight, defaults.weight)
3010
+ );
3011
+ element.style.color = style?.color ?? '';
3012
+ element.style.textAlign = style?.alignment ?? defaults.alignment;
3013
+ element.style.whiteSpace = style?.lines === 2 ? 'normal' : 'nowrap';
3014
+ element.style.display = '';
3015
+ element.style.removeProperty('-webkit-line-clamp');
3016
+ element.style.removeProperty('-webkit-box-orient');
3017
+ if (style?.lines === 2) {
3018
+ element.style.display = '-webkit-box';
3019
+ element.style.setProperty('-webkit-line-clamp', '2');
3020
+ element.style.setProperty('-webkit-box-orient', 'vertical');
3021
+ }
3022
+ }
3023
+
3024
+ function setMarketQuoteContent(element: HTMLElement, row: MarketRow) {
3025
+ const style = row.style;
3026
+ const layoutStyle = resolveWebMarketLayoutStyle(row);
3027
+ const price = element.querySelector<HTMLElement>(
3028
+ '.ok-native-list-market-price'
3029
+ );
3030
+ if (price) {
3031
+ price.textContent = row.price;
3032
+ applyMarketTextStyle(price, style?.price, {
3033
+ fontSize: 16,
3034
+ lineHeight: 24,
3035
+ weight: 500,
3036
+ alignment: 'end',
3037
+ });
3038
+ applyValueSegments(
3039
+ price,
3040
+ row.priceSegments,
3041
+ style?.price?.fontSize ?? 16,
3042
+ style?.price?.lineHeight ?? 24,
3043
+ marketFontWeight(style?.price?.fontWeight, 500)
3044
+ );
3045
+ }
3046
+ const change = element.querySelector<HTMLElement>(
3047
+ '.ok-native-list-market-change'
3048
+ );
3049
+ if (change) {
3050
+ change.textContent = row.change.text;
3051
+ setData(change, 'tone', row.change.tone);
3052
+ applyMarketTextStyle(change, style?.change, {
3053
+ fontSize: 14,
3054
+ lineHeight: 20,
3055
+ weight: 500,
3056
+ alignment: 'center',
3057
+ });
3058
+ applyValueSegments(
3059
+ change,
3060
+ row.change.textSegments,
3061
+ style?.change?.fontSize ?? 14,
3062
+ style?.change?.lineHeight ?? 20,
3063
+ marketFontWeight(style?.change?.fontWeight, 500)
3064
+ );
3065
+ change.style.width = String(layoutStyle.changeWidth) + 'px';
3066
+ change.style.height = String(layoutStyle.changeHeight) + 'px';
3067
+ change.style.borderRadius = String(layoutStyle.changeCornerRadius) + 'px';
3068
+ change.style.color = row.change.textColor ?? style?.change?.color ?? '';
3069
+ change.style.background = row.change.backgroundColor ?? '';
3070
+ }
3071
+ element.setAttribute(
3072
+ 'aria-label',
3073
+ row.accessibilityLabel ??
3074
+ [row.title, row.subtitle, row.price, row.change.text]
3075
+ .filter(Boolean)
3076
+ .join(', ')
3077
+ );
3078
+ }
3079
+
3080
+ function createMarketRow(context: RenderContext, row: MarketRow): HTMLElement {
3081
+ const body = createElement(
3082
+ context.document,
3083
+ 'div',
3084
+ 'ok-native-list-row ok-native-list-market'
3085
+ );
3086
+ setData(body, 'variant', row.variant);
3087
+ const style = row.style;
3088
+ const layoutStyle = resolveWebMarketLayoutStyle(row);
3089
+ body.style.padding =
3090
+ String(layoutStyle.verticalPadding) +
3091
+ 'px ' +
3092
+ String(layoutStyle.horizontalPadding) +
3093
+ 'px';
3094
+ body.style.gap = '0px';
3095
+ const visual = createVisual(context, row.leading);
3096
+ if (visual) {
3097
+ const width = layoutStyle.imageWidth;
3098
+ const height = layoutStyle.imageHeight;
3099
+ visual.style.width = String(width) + 'px';
3100
+ visual.style.height = String(height) + 'px';
3101
+ visual.style.flexBasis = String(width) + 'px';
3102
+ visual.style.borderRadius = String(layoutStyle.imageCornerRadius) + 'px';
3103
+ const borderColor = 'borderColor' in row.leading ? row.leading.borderColor : undefined;
3104
+ if (borderColor) {
3105
+ visual.style.border = '1px solid ' + borderColor;
3106
+ visual.style.boxSizing = 'border-box';
3107
+ }
3108
+ visual.querySelectorAll<HTMLElement>('img').forEach((image) => {
3109
+ if (!image.classList.contains('ok-native-list-visual-corner')) {
3110
+ image.style.width = '100%';
3111
+ image.style.height = '100%';
3112
+ if (borderColor) {
3113
+ image.style.borderRadius = '0';
3114
+ image.style.clipPath = 'inset(-1px round ' + String(layoutStyle.imageCornerRadius) + 'px)';
3115
+ }
3116
+ image.style.objectFit =
3117
+ style?.image?.contentFit ?? image.style.objectFit;
3118
+ } else {
3119
+ image.style.width = '20px';
3120
+ image.style.height = '20px';
3121
+ if (borderColor) {
3122
+ image.style.right = '-5px';
3123
+ image.style.bottom = '-5px';
3124
+ }
3125
+ }
3126
+ });
3127
+ visual.style.marginRight = String(layoutStyle.leadingGap) + 'px';
3128
+ body.appendChild(visual);
3129
+ }
3130
+ const main = createElement(
3131
+ context.document,
3132
+ 'span',
3133
+ 'ok-native-list-market-main'
3134
+ );
3135
+ main.style.gap = String(style?.lineGap ?? 0) + 'px';
3136
+ const titleLine = createElement(
3137
+ context.document,
3138
+ 'span',
3139
+ 'ok-native-list-market-title-line'
3140
+ );
3141
+ titleLine.style.gap = String(layoutStyle.titleBadgeGap) + 'px';
3142
+ const title = createElement(
3143
+ context.document,
3144
+ 'span',
3145
+ 'ok-native-list-market-title',
3146
+ row.title
3147
+ );
3148
+ applyMarketTextStyle(title, style?.title, {
3149
+ fontSize: 16,
3150
+ lineHeight: 24,
3151
+ weight: 500,
3152
+ alignment: 'start',
3153
+ });
3154
+ titleLine.appendChild(title);
3155
+ row.badges?.forEach((badge, slot) => {
3156
+ const element = createElement(
3157
+ context.document,
3158
+ badge.actionKey ? 'button' : 'span',
3159
+ 'ok-native-list-market-badge',
3160
+ badge.text
3161
+ );
3162
+ if (badge.actionKey) {
3163
+ element.setAttribute('type', 'button');
3164
+ setData(element, 'nativeListAction', badge.actionKey);
3165
+ markActionAnchorSource(element, 'marketBadge', slot);
3166
+ }
3167
+ setData(element, 'tone', badge.tone);
3168
+ element.style.color =
3169
+ badge.textColor ??
3170
+ (badge.tone === 'success'
3171
+ ? 'var(--nl-positive)'
3172
+ : badge.tone === 'danger'
3173
+ ? 'var(--nl-negative)'
3174
+ : badge.tone === 'info'
3175
+ ? 'var(--nl-info)'
3176
+ : badge.tone === 'warning'
3177
+ ? 'var(--nl-primary)'
3178
+ : '');
3179
+ element.style.background =
3180
+ badge.backgroundColor ??
3181
+ ((badge.icon || badge.iconName) && !badge.text ? 'transparent' : '');
3182
+ if ((badge.icon || badge.iconName) && !badge.text) {
3183
+ element.style.padding = '0';
3184
+ }
3185
+ if (badge.accessibilityLabel)
3186
+ element.setAttribute('aria-label', badge.accessibilityLabel);
3187
+ if (badge.icon) {
3188
+ const icon = createImage(context, badge.icon);
3189
+ if (icon) {
3190
+ icon.style.borderRadius = '50%';
3191
+ element.prepend(icon);
3192
+ }
3193
+ }
3194
+ if (badge.iconName === 'verified') {
3195
+ const icon = createElement(
3196
+ context.document,
3197
+ 'span',
3198
+ 'ok-native-list-market-badge-icon'
3199
+ );
3200
+ applySelectorIcon(icon, 'BadgeVerifiedSolid');
3201
+ element.prepend(icon);
3202
+ }
3203
+ titleLine.appendChild(element);
3204
+ });
3205
+ main.appendChild(titleLine);
3206
+ if (row.subtitle || row.subtitleSegments?.length) {
3207
+ const subtitle = createElement(
3208
+ context.document,
3209
+ 'span',
3210
+ 'ok-native-list-market-subtitle',
3211
+ row.subtitle
3212
+ );
3213
+ applyMarketTextStyle(subtitle, style?.subtitle, {
3214
+ fontSize: 14,
3215
+ lineHeight: 20,
3216
+ weight: 400,
3217
+ alignment: 'start',
3218
+ });
3219
+ applyValueSegments(
3220
+ subtitle,
3221
+ row.subtitleSegments,
3222
+ style?.subtitle?.fontSize ?? 14,
3223
+ style?.subtitle?.lineHeight ?? 20,
3224
+ marketFontWeight(style?.subtitle?.fontWeight, 400)
3225
+ );
3226
+ main.appendChild(subtitle);
3227
+ }
3228
+ body.appendChild(main);
3229
+ const trailing = createElement(
3230
+ context.document,
3231
+ 'span',
3232
+ 'ok-native-list-market-trailing'
3233
+ );
3234
+ trailing.style.gap = String(layoutStyle.trailingGap) + 'px';
3235
+ trailing.append(
3236
+ createElement(context.document, 'span', 'ok-native-list-market-price'),
3237
+ createElement(context.document, 'span', 'ok-native-list-market-change')
3238
+ );
3239
+ body.appendChild(trailing);
3240
+ setMarketQuoteContent(body, row);
3241
+ return body;
3242
+ }
3243
+
2787
3244
  function createRowBody(context: RenderContext, row: RowModel): HTMLElement {
2788
3245
  switch (row.type) {
2789
3246
  case 'walletGroup':
@@ -2802,6 +3259,8 @@ function createRowBody(context: RenderContext, row: RowModel): HTMLElement {
2802
3259
  return createMetricRow(context, row);
2803
3260
  case 'dataRow':
2804
3261
  return createDataRow(context, row);
3262
+ case 'market':
3263
+ return createMarketRow(context, row);
2805
3264
  case 'identity':
2806
3265
  case 'activity':
2807
3266
  case 'message':
@@ -2869,6 +3328,16 @@ export class NativeListWebEngine {
2869
3328
  );
2870
3329
  private actionAnchorCounter = 0;
2871
3330
  private bindingEpochCounter = 0;
3331
+ private marketPointer:
3332
+ | Readonly<{
3333
+ pointerId: number;
3334
+ rowKey: string;
3335
+ startX: number;
3336
+ startY: number;
3337
+ timer: number;
3338
+ }>
3339
+ | undefined;
3340
+ private marketLongPressFired = false;
2872
3341
  private lastViewportWidth = -1;
2873
3342
  private lastViewportHeight = -1;
2874
3343
  private destroyed = false;
@@ -2959,6 +3428,10 @@ export class NativeListWebEngine {
2959
3428
  passive: true,
2960
3429
  });
2961
3430
  this.root.addEventListener('click', this.handleClick);
3431
+ this.root.addEventListener('pointerdown', this.handleMarketPointerDown);
3432
+ this.root.addEventListener('pointermove', this.handleMarketPointerMove);
3433
+ this.root.addEventListener('pointerup', this.handleMarketPointerEnd);
3434
+ this.root.addEventListener('pointercancel', this.handleMarketPointerEnd);
2962
3435
  // OneKey patch: preserve web tooltip hover for section titles.
2963
3436
  this.root.addEventListener('pointerover', this.handleTitlePointerOver);
2964
3437
  this.root.addEventListener('pointerout', this.handleTitlePointerOut);
@@ -3034,7 +3507,6 @@ export class NativeListWebEngine {
3034
3507
  applyPatches(patches: readonly RowPatch[]) {
3035
3508
  if (patches.length === 0) return;
3036
3509
  const next = applyRowPatches(this.snapshot, patches);
3037
- this.invalidateActionAnchor('snapshot');
3038
3510
  const selection = new Set(this.selectedKeys);
3039
3511
  patches.forEach((patch) => {
3040
3512
  const selected =
@@ -3042,6 +3514,29 @@ export class NativeListWebEngine {
3042
3514
  if (selected === true) selection.add(patch.key);
3043
3515
  else if (selected === false) selection.delete(patch.key);
3044
3516
  });
3517
+ if (webMarketImageBindDeltaForPatches(patches) === 0) {
3518
+ this.snapshot = next;
3519
+ this.rows = effectiveRows(next);
3520
+ this.selectedKeys = selection;
3521
+ patches.forEach((patch) => {
3522
+ const index = this.rows.findIndex((row) => row.key === patch.key);
3523
+ const row = this.rows[index];
3524
+ const element = this.mounted.get(index);
3525
+ if (row?.type === 'market' && element) {
3526
+ setMarketQuoteContent(element, row);
3527
+ element.dataset.renderSignature = webRowRenderSignature(row);
3528
+ }
3529
+ if (
3530
+ row?.type === 'market' &&
3531
+ this.sticky.dataset.nativeListRowKey === row.key
3532
+ ) {
3533
+ setMarketQuoteContent(this.sticky, row);
3534
+ this.sticky.dataset.renderSignature = webRowRenderSignature(row);
3535
+ }
3536
+ });
3537
+ return;
3538
+ }
3539
+ this.invalidateActionAnchor('snapshot');
3045
3540
  this.setSnapshot(next, selection);
3046
3541
  }
3047
3542
 
@@ -3180,6 +3675,11 @@ export class NativeListWebEngine {
3180
3675
  );
3181
3676
  this.viewport.removeEventListener('scroll', this.handleScroll);
3182
3677
  this.root.removeEventListener('click', this.handleClick);
3678
+ this.cancelMarketPointer();
3679
+ this.root.removeEventListener('pointerdown', this.handleMarketPointerDown);
3680
+ this.root.removeEventListener('pointermove', this.handleMarketPointerMove);
3681
+ this.root.removeEventListener('pointerup', this.handleMarketPointerEnd);
3682
+ this.root.removeEventListener('pointercancel', this.handleMarketPointerEnd);
3183
3683
  this.root.removeEventListener('pointerover', this.handleTitlePointerOver);
3184
3684
  this.root.removeEventListener('pointerout', this.handleTitlePointerOut);
3185
3685
  this.root.removeEventListener('keydown', this.handleKeyDown);
@@ -3230,6 +3730,7 @@ export class NativeListWebEngine {
3230
3730
  snapshot: NativeListSnapshot,
3231
3731
  selectedKeys?: ReadonlySet<string>
3232
3732
  ) {
3733
+ this.cancelMarketPointer();
3233
3734
  this.measuredWarningHeights.clear();
3234
3735
  this.snapshot = snapshot;
3235
3736
  this.rows = effectiveRows(snapshot);
@@ -3580,6 +4081,17 @@ export class NativeListWebEngine {
3580
4081
  }
3581
4082
  }
3582
4083
  element.replaceChildren(body);
4084
+ if (
4085
+ row.type === 'market' &&
4086
+ row.diagnostics?.imageBindActionKey &&
4087
+ body.querySelector('img')
4088
+ ) {
4089
+ this.callbacks.onRowAction?.({
4090
+ rowKey: row.key,
4091
+ actionKey: row.diagnostics.imageBindActionKey,
4092
+ sectionKey: row.sectionKey,
4093
+ });
4094
+ }
3583
4095
  }
3584
4096
 
3585
4097
  private renderFooter() {
@@ -4102,6 +4614,8 @@ export class NativeListWebEngine {
4102
4614
  ? row.actionKey
4103
4615
  : row.type === 'system' && row.variant === 'retry'
4104
4616
  ? row.actionKey
4617
+ : row.type === 'market' && row.pressActionKey
4618
+ ? row.pressActionKey
4105
4619
  : 'press';
4106
4620
  if (rowElement && sourceElement) {
4107
4621
  const anchor = this.createActionAnchor(sourceElement, rowElement, 'row');
@@ -4116,6 +4630,64 @@ export class NativeListWebEngine {
4116
4630
  }
4117
4631
  }
4118
4632
 
4633
+ private cancelMarketPointer() {
4634
+ const state = this.marketPointer;
4635
+ if (!state) return;
4636
+ this.document.defaultView?.clearTimeout(state.timer);
4637
+ this.marketPointer = undefined;
4638
+ }
4639
+
4640
+ private handleMarketPointerDown = (event: PointerEvent) => {
4641
+ if (event.button !== 0 || !(event.target instanceof Element)) return;
4642
+ this.marketLongPressFired = false;
4643
+ if (event.target.closest('[data-native-list-action]')) return;
4644
+ const rowElement = event.target.closest<HTMLElement>(
4645
+ '[data-native-list-row-key]'
4646
+ );
4647
+ const row = this.rowAtElement(rowElement);
4648
+ if (row?.type !== 'market' || row.disabled) return;
4649
+ this.cancelMarketPointer();
4650
+ if (row.pressInActionKey)
4651
+ this.emitRowAction(
4652
+ row,
4653
+ row.pressInActionKey,
4654
+ rowElement ?? undefined,
4655
+ rowElement ?? undefined
4656
+ );
4657
+ if (!row.longPressActionKey || !rowElement) return;
4658
+ markActionAnchorSource(rowElement, 'row');
4659
+ const timer = this.document.defaultView?.setTimeout(() => {
4660
+ const current = this.marketPointer;
4661
+ if (!current || current.pointerId !== event.pointerId) return;
4662
+ this.marketPointer = undefined;
4663
+ this.marketLongPressFired = true;
4664
+ this.emitRowAction(row, row.longPressActionKey!, rowElement, rowElement);
4665
+ }, MARKET_LONG_PRESS_MS);
4666
+ if (timer === undefined) return;
4667
+ this.marketPointer = {
4668
+ pointerId: event.pointerId,
4669
+ rowKey: row.key,
4670
+ startX: event.clientX,
4671
+ startY: event.clientY,
4672
+ timer,
4673
+ };
4674
+ };
4675
+
4676
+ private handleMarketPointerMove = (event: PointerEvent) => {
4677
+ const state = this.marketPointer;
4678
+ if (!state || state.pointerId !== event.pointerId) return;
4679
+ if (
4680
+ Math.abs(event.clientX - state.startX) > MARKET_MOVE_CANCEL_PX ||
4681
+ Math.abs(event.clientY - state.startY) > MARKET_MOVE_CANCEL_PX
4682
+ )
4683
+ this.cancelMarketPointer();
4684
+ };
4685
+
4686
+ private handleMarketPointerEnd = (event: PointerEvent) => {
4687
+ if (this.marketPointer?.pointerId === event.pointerId)
4688
+ this.cancelMarketPointer();
4689
+ };
4690
+
4119
4691
  // OneKey patch: hover opens the same anchored help action as native taps.
4120
4692
  private handleTitlePointerOver = (event: PointerEvent) => {
4121
4693
  const target = event.target;
@@ -4167,7 +4739,8 @@ export class NativeListWebEngine {
4167
4739
  };
4168
4740
 
4169
4741
  private handleClick = (event: Event) => {
4170
- if (Date.now() < this.suppressClickUntil) {
4742
+ if (this.marketLongPressFired || Date.now() < this.suppressClickUntil) {
4743
+ this.marketLongPressFired = false;
4171
4744
  event.preventDefault();
4172
4745
  event.stopPropagation();
4173
4746
  return;
@@ -4340,6 +4913,7 @@ export class NativeListWebEngine {
4340
4913
  }
4341
4914
 
4342
4915
  private handleScroll = () => {
4916
+ this.cancelMarketPointer();
4343
4917
  this.invalidateActionAnchor('scroll');
4344
4918
  this.scheduleFrame();
4345
4919
  };