@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.
@@ -20,6 +20,9 @@ export const WEB_REORDER_ANIMATION = {
20
20
  dropTimingFunction: 'ease'
21
21
  };
22
22
  const REORDER_TOUCH_LONG_PRESS_MS = 120;
23
+ const MARKET_LONG_PRESS_MS = 800;
24
+ const MARKET_MOVE_CANCEL_PX = 10;
25
+ export const WEB_MARKET_VERIFIED_PATH = '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';
23
26
  const REORDER_MOUSE_MOVE_THRESHOLD_PX = 5;
24
27
  const REORDER_EDGE_RATIO = 0.25;
25
28
  const REORDER_MAX_SPEED_ZONE_RATIO = 0.05;
@@ -50,6 +53,25 @@ const defaultTheme = {
50
53
  inverseText: '#FCFCFC',
51
54
  info: '#0D74CE'
52
55
  };
56
+ /** Resolves every reusable Market geometry field, including legacy defaults. */
57
+ export function resolveWebMarketLayoutStyle(row) {
58
+ const style = row.style;
59
+ const imageWidth = style?.image?.width ?? (row.variant === 'stock' ? 40 : 32);
60
+ const imageHeight = style?.image?.height ?? (row.variant === 'stock' ? 40 : 32);
61
+ return {
62
+ horizontalPadding: style?.horizontalPadding ?? (row.variant === 'perp' ? 16 : 20),
63
+ verticalPadding: style?.verticalPadding ?? 12,
64
+ leadingGap: style?.leadingGap ?? (row.variant === 'perp' ? 8 : 14),
65
+ titleBadgeGap: style?.titleBadgeGap ?? 4,
66
+ trailingGap: style?.trailingGap ?? 8,
67
+ imageWidth,
68
+ imageHeight,
69
+ imageCornerRadius: style?.image?.cornerRadius ?? (style?.image?.shape === 'square' ? 0 : style?.image?.shape === 'rounded' ? 8 : Math.min(imageWidth, imageHeight) / 2),
70
+ changeWidth: style?.changeWidth ?? 80,
71
+ changeHeight: style?.changeHeight ?? 32,
72
+ changeCornerRadius: style?.changeCornerRadius ?? 8
73
+ };
74
+ }
53
75
  export function webActionAnchorPayload(token, rect, source, generation, layoutDirection, slot) {
54
76
  return {
55
77
  token,
@@ -195,7 +217,7 @@ export function estimateWebRowHeight(row, snapshot, availableWidth) {
195
217
  break;
196
218
  }
197
219
  case 'system':
198
- base = row.variant === 'noMatch' || row.variant === 'end' ? 36 : row.variant === 'retry' ? 44 : 56;
220
+ base = row.variant === 'loading' && row.loadingStyle === 'skeleton' ? 56 : row.variant === 'loading' && row.loadingStyle === 'spinner' ? 52 : 'presentation' in row && row.presentation === 'market' ? row.variant === 'loading' ? 68 : 44 : row.variant === 'noMatch' || row.variant === 'end' ? 36 : row.variant === 'retry' ? 44 : 56;
199
221
  break;
200
222
  case 'action':
201
223
  base = row.presentation === 'accountSelector' ? 48 : row.icon ? 60 : 44;
@@ -203,6 +225,12 @@ export function estimateWebRowHeight(row, snapshot, availableWidth) {
203
225
  case 'dataRow':
204
226
  base = row.columns.some(column => column.secondaryText) ? 60 : 56;
205
227
  break;
228
+ case 'market':
229
+ {
230
+ const marketStyle = resolveWebMarketLayoutStyle(row);
231
+ base = Math.max(row.variant === 'stock' ? 72 : 68, marketStyle.imageHeight + marketStyle.verticalPadding * 2);
232
+ break;
233
+ }
206
234
  case 'identity':
207
235
  base = row.tertiary ? 72 : row.subtitle ? 60 : 56;
208
236
  break;
@@ -391,6 +419,15 @@ function rowWithoutSelectionState(row) {
391
419
  export function webRowRenderSignature(row) {
392
420
  return JSON.stringify(rowWithoutSelectionState(row));
393
421
  }
422
+ const MARKET_QUOTE_PATCH_FIELDS = new Set(['revision', 'price', 'priceSegments', 'change', 'accessibilityLabel']);
423
+ export function isWebMarketQuotePatch(patch) {
424
+ return patch.type === 'market' && Object.keys(patch.changes).every(key => MARKET_QUOTE_PATCH_FIELDS.has(key));
425
+ }
426
+
427
+ /** Number of Market image-binding passes caused by one Web patch transaction. */
428
+ export function webMarketImageBindDeltaForPatches(patches) {
429
+ return patches.length > 0 && patches.every(isWebMarketQuotePatch) ? 0 : 1;
430
+ }
394
431
 
395
432
  // OneKey patch: selector names must shrink before the sidebar clips their contents.
396
433
  export const WEB_LIST_CSS = `
@@ -448,11 +485,14 @@ export const WEB_LIST_CSS = `
448
485
  .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}
449
486
  .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)}
450
487
  .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}
488
+ .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)}
489
+ .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}
451
490
  .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)}
452
491
  .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)}
453
492
  .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)}
454
493
  .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}
455
- @media (prefers-reduced-motion:reduce){.ok-native-list-index-preview,.ok-native-list-refresh{transition:none}.ok-native-list-spinner{animation:none}}
494
+ .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}
495
+ @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)}}
456
496
  /* OneKey patch: selector controls follow their original semantic colors and geometry. */
457
497
  .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))}
458
498
  .ok-native-list-checkbox[data-selector="networkSelector"]::after{display:none}
@@ -812,6 +852,15 @@ const selectorIcons = {
812
852
  fillRule: 'nonzero',
813
853
  opacity: 1
814
854
  }]
855
+ },
856
+ BadgeVerifiedSolid: {
857
+ viewBox: '0 0 24 24',
858
+ paths: [{
859
+ d: WEB_MARKET_VERIFIED_PATH,
860
+ fill: 'currentColor',
861
+ fillRule: 'evenodd',
862
+ opacity: 1
863
+ }]
815
864
  }
816
865
  };
817
866
  function applySelectorIcon(element, name) {
@@ -859,6 +908,7 @@ function visualFromRow(row) {
859
908
  if (row.type === 'activity') return row.leading;
860
909
  if (row.type === 'message') return row.leading;
861
910
  if (row.type === 'dataRow') return row.leading;
911
+ if (row.type === 'market') return row.leading;
862
912
  if (row.type === 'metricCard') return row.visual;
863
913
  return undefined;
864
914
  }
@@ -1266,6 +1316,69 @@ function createActionRow(context, row) {
1266
1316
  function createSystemRow(context, row) {
1267
1317
  const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-system');
1268
1318
  setData(body, 'variant', row.variant);
1319
+ if ('presentation' in row) setData(body, 'nativeListPresentation', row.presentation);
1320
+ if (row.variant === 'loading' && row.loadingStyle === 'skeleton') {
1321
+ body.classList.add('ok-native-list-market-skeleton');
1322
+ const background = resolvedTheme(context.snapshot).background;
1323
+ const rgb = Number.parseInt(background.slice(1, 7), 16);
1324
+ const dark = (rgb >> 16 & 255) * 0.299 + (rgb >> 8 & 255) * 0.587 + (rgb & 255) * 0.114 < 128;
1325
+ body.style.setProperty('--nl-skeleton-base', dark ? '#111111' : '#fafafa');
1326
+ body.style.setProperty('--nl-skeleton-highlight', dark ? '#333333' : '#cdcdcd');
1327
+ const left = createElement(context.document, 'div', 'ok-native-list-skeleton-left');
1328
+ const mark = (width, height, circle = false) => {
1329
+ const element = createElement(context.document, 'span', 'ok-native-list-skeleton-mark');
1330
+ element.style.width = `${width}px`;
1331
+ element.style.height = `${height}px`;
1332
+ if (circle) element.style.borderRadius = '50%';
1333
+ return element;
1334
+ };
1335
+ left.appendChild(mark(32, 32, true));
1336
+ const text = createElement(context.document, 'div', 'ok-native-list-skeleton-text');
1337
+ text.appendChild(mark(80, 16));
1338
+ text.appendChild(mark(60, 12));
1339
+ left.appendChild(text);
1340
+ body.appendChild(left);
1341
+ const right = createElement(context.document, 'div', 'ok-native-list-skeleton-right');
1342
+ right.appendChild(mark(80, 18));
1343
+ right.appendChild(mark(80, 18));
1344
+ body.appendChild(right);
1345
+ return body;
1346
+ }
1347
+ if (row.variant === 'loading' && row.loadingStyle === 'spinner') {
1348
+ body.style.justifyContent = 'center';
1349
+ body.style.padding = '16px';
1350
+ const spinner = createElement(context.document, 'span', 'ok-native-list-market-spinner');
1351
+ spinner.setAttribute('role', 'progressbar');
1352
+ // Same 20px SVG and 750ms rotation as react-native-web ActivityIndicator.
1353
+ spinner.innerHTML = '<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>';
1354
+ body.appendChild(spinner);
1355
+ return body;
1356
+ }
1357
+ if ('presentation' in row && row.presentation === 'market') {
1358
+ if (row.variant === 'noMatch') {
1359
+ body.style.justifyContent = 'center';
1360
+ body.style.padding = '32px';
1361
+ const message = createElement(context.document, 'span', '', row.message);
1362
+ message.style.fontSize = '16px';
1363
+ message.style.lineHeight = '24px';
1364
+ body.appendChild(message);
1365
+ return body;
1366
+ }
1367
+ if (row.variant === 'end') {
1368
+ body.style.justifyContent = 'center';
1369
+ body.style.padding = '16px';
1370
+ body.style.gap = '8px';
1371
+ for (const width of [80, 4, 80]) {
1372
+ const mark = createElement(context.document, 'span', '');
1373
+ mark.style.width = String(width) + 'px';
1374
+ mark.style.height = width === 4 ? '4px' : '1px';
1375
+ mark.style.borderRadius = width === 4 ? '2px' : '0';
1376
+ mark.style.background = 'var(--nl-separator)';
1377
+ body.appendChild(mark);
1378
+ }
1379
+ return body;
1380
+ }
1381
+ }
1269
1382
  if (row.variant === 'warning') {
1270
1383
  body.classList.add('ok-native-list-warning');
1271
1384
  body.style.borderColor = row.borderColor ?? 'var(--nl-separator)';
@@ -1555,6 +1668,163 @@ function createWalletGroupRow(context, row) {
1555
1668
  });
1556
1669
  return body;
1557
1670
  }
1671
+ function marketFontWeight(value, fallback) {
1672
+ if (value === 'bold') return 700;
1673
+ if (value === 'semibold') return 600;
1674
+ if (value === 'medium') return 500;
1675
+ if (value === 'regular') return 400;
1676
+ return fallback;
1677
+ }
1678
+ function applyMarketTextStyle(element, style, defaults) {
1679
+ element.style.fontSize = String(style?.fontSize ?? defaults.fontSize) + 'px';
1680
+ element.style.lineHeight = String(style?.lineHeight ?? defaults.lineHeight) + 'px';
1681
+ element.style.fontWeight = String(marketFontWeight(style?.fontWeight, defaults.weight));
1682
+ element.style.color = style?.color ?? '';
1683
+ element.style.textAlign = style?.alignment ?? defaults.alignment;
1684
+ element.style.whiteSpace = style?.lines === 2 ? 'normal' : 'nowrap';
1685
+ element.style.display = '';
1686
+ element.style.removeProperty('-webkit-line-clamp');
1687
+ element.style.removeProperty('-webkit-box-orient');
1688
+ if (style?.lines === 2) {
1689
+ element.style.display = '-webkit-box';
1690
+ element.style.setProperty('-webkit-line-clamp', '2');
1691
+ element.style.setProperty('-webkit-box-orient', 'vertical');
1692
+ }
1693
+ }
1694
+ function setMarketQuoteContent(element, row) {
1695
+ const style = row.style;
1696
+ const layoutStyle = resolveWebMarketLayoutStyle(row);
1697
+ const price = element.querySelector('.ok-native-list-market-price');
1698
+ if (price) {
1699
+ price.textContent = row.price;
1700
+ applyMarketTextStyle(price, style?.price, {
1701
+ fontSize: 16,
1702
+ lineHeight: 24,
1703
+ weight: 500,
1704
+ alignment: 'end'
1705
+ });
1706
+ applyValueSegments(price, row.priceSegments, style?.price?.fontSize ?? 16, style?.price?.lineHeight ?? 24, marketFontWeight(style?.price?.fontWeight, 500));
1707
+ }
1708
+ const change = element.querySelector('.ok-native-list-market-change');
1709
+ if (change) {
1710
+ change.textContent = row.change.text;
1711
+ setData(change, 'tone', row.change.tone);
1712
+ applyMarketTextStyle(change, style?.change, {
1713
+ fontSize: 14,
1714
+ lineHeight: 20,
1715
+ weight: 500,
1716
+ alignment: 'center'
1717
+ });
1718
+ applyValueSegments(change, row.change.textSegments, style?.change?.fontSize ?? 14, style?.change?.lineHeight ?? 20, marketFontWeight(style?.change?.fontWeight, 500));
1719
+ change.style.width = String(layoutStyle.changeWidth) + 'px';
1720
+ change.style.height = String(layoutStyle.changeHeight) + 'px';
1721
+ change.style.borderRadius = String(layoutStyle.changeCornerRadius) + 'px';
1722
+ change.style.color = row.change.textColor ?? style?.change?.color ?? '';
1723
+ change.style.background = row.change.backgroundColor ?? '';
1724
+ }
1725
+ element.setAttribute('aria-label', row.accessibilityLabel ?? [row.title, row.subtitle, row.price, row.change.text].filter(Boolean).join(', '));
1726
+ }
1727
+ function createMarketRow(context, row) {
1728
+ const body = createElement(context.document, 'div', 'ok-native-list-row ok-native-list-market');
1729
+ setData(body, 'variant', row.variant);
1730
+ const style = row.style;
1731
+ const layoutStyle = resolveWebMarketLayoutStyle(row);
1732
+ body.style.padding = String(layoutStyle.verticalPadding) + 'px ' + String(layoutStyle.horizontalPadding) + 'px';
1733
+ body.style.gap = '0px';
1734
+ const visual = createVisual(context, row.leading);
1735
+ if (visual) {
1736
+ const width = layoutStyle.imageWidth;
1737
+ const height = layoutStyle.imageHeight;
1738
+ visual.style.width = String(width) + 'px';
1739
+ visual.style.height = String(height) + 'px';
1740
+ visual.style.flexBasis = String(width) + 'px';
1741
+ visual.style.borderRadius = String(layoutStyle.imageCornerRadius) + 'px';
1742
+ const borderColor = 'borderColor' in row.leading ? row.leading.borderColor : undefined;
1743
+ if (borderColor) {
1744
+ visual.style.border = '1px solid ' + borderColor;
1745
+ visual.style.boxSizing = 'border-box';
1746
+ }
1747
+ visual.querySelectorAll('img').forEach(image => {
1748
+ if (!image.classList.contains('ok-native-list-visual-corner')) {
1749
+ image.style.width = '100%';
1750
+ image.style.height = '100%';
1751
+ if (borderColor) {
1752
+ image.style.borderRadius = '0';
1753
+ image.style.clipPath = 'inset(-1px round ' + String(layoutStyle.imageCornerRadius) + 'px)';
1754
+ }
1755
+ image.style.objectFit = style?.image?.contentFit ?? image.style.objectFit;
1756
+ } else {
1757
+ image.style.width = '20px';
1758
+ image.style.height = '20px';
1759
+ if (borderColor) {
1760
+ image.style.right = '-5px';
1761
+ image.style.bottom = '-5px';
1762
+ }
1763
+ }
1764
+ });
1765
+ visual.style.marginRight = String(layoutStyle.leadingGap) + 'px';
1766
+ body.appendChild(visual);
1767
+ }
1768
+ const main = createElement(context.document, 'span', 'ok-native-list-market-main');
1769
+ main.style.gap = String(style?.lineGap ?? 0) + 'px';
1770
+ const titleLine = createElement(context.document, 'span', 'ok-native-list-market-title-line');
1771
+ titleLine.style.gap = String(layoutStyle.titleBadgeGap) + 'px';
1772
+ const title = createElement(context.document, 'span', 'ok-native-list-market-title', row.title);
1773
+ applyMarketTextStyle(title, style?.title, {
1774
+ fontSize: 16,
1775
+ lineHeight: 24,
1776
+ weight: 500,
1777
+ alignment: 'start'
1778
+ });
1779
+ titleLine.appendChild(title);
1780
+ row.badges?.forEach((badge, slot) => {
1781
+ const element = createElement(context.document, badge.actionKey ? 'button' : 'span', 'ok-native-list-market-badge', badge.text);
1782
+ if (badge.actionKey) {
1783
+ element.setAttribute('type', 'button');
1784
+ setData(element, 'nativeListAction', badge.actionKey);
1785
+ markActionAnchorSource(element, 'marketBadge', slot);
1786
+ }
1787
+ setData(element, 'tone', badge.tone);
1788
+ element.style.color = badge.textColor ?? (badge.tone === 'success' ? 'var(--nl-positive)' : badge.tone === 'danger' ? 'var(--nl-negative)' : badge.tone === 'info' ? 'var(--nl-info)' : badge.tone === 'warning' ? 'var(--nl-primary)' : '');
1789
+ element.style.background = badge.backgroundColor ?? ((badge.icon || badge.iconName) && !badge.text ? 'transparent' : '');
1790
+ if ((badge.icon || badge.iconName) && !badge.text) {
1791
+ element.style.padding = '0';
1792
+ }
1793
+ if (badge.accessibilityLabel) element.setAttribute('aria-label', badge.accessibilityLabel);
1794
+ if (badge.icon) {
1795
+ const icon = createImage(context, badge.icon);
1796
+ if (icon) {
1797
+ icon.style.borderRadius = '50%';
1798
+ element.prepend(icon);
1799
+ }
1800
+ }
1801
+ if (badge.iconName === 'verified') {
1802
+ const icon = createElement(context.document, 'span', 'ok-native-list-market-badge-icon');
1803
+ applySelectorIcon(icon, 'BadgeVerifiedSolid');
1804
+ element.prepend(icon);
1805
+ }
1806
+ titleLine.appendChild(element);
1807
+ });
1808
+ main.appendChild(titleLine);
1809
+ if (row.subtitle || row.subtitleSegments?.length) {
1810
+ const subtitle = createElement(context.document, 'span', 'ok-native-list-market-subtitle', row.subtitle);
1811
+ applyMarketTextStyle(subtitle, style?.subtitle, {
1812
+ fontSize: 14,
1813
+ lineHeight: 20,
1814
+ weight: 400,
1815
+ alignment: 'start'
1816
+ });
1817
+ applyValueSegments(subtitle, row.subtitleSegments, style?.subtitle?.fontSize ?? 14, style?.subtitle?.lineHeight ?? 20, marketFontWeight(style?.subtitle?.fontWeight, 400));
1818
+ main.appendChild(subtitle);
1819
+ }
1820
+ body.appendChild(main);
1821
+ const trailing = createElement(context.document, 'span', 'ok-native-list-market-trailing');
1822
+ trailing.style.gap = String(layoutStyle.trailingGap) + 'px';
1823
+ trailing.append(createElement(context.document, 'span', 'ok-native-list-market-price'), createElement(context.document, 'span', 'ok-native-list-market-change'));
1824
+ body.appendChild(trailing);
1825
+ setMarketQuoteContent(body, row);
1826
+ return body;
1827
+ }
1558
1828
  function createRowBody(context, row) {
1559
1829
  switch (row.type) {
1560
1830
  case 'walletGroup':
@@ -1573,6 +1843,8 @@ function createRowBody(context, row) {
1573
1843
  return createMetricRow(context, row);
1574
1844
  case 'dataRow':
1575
1845
  return createDataRow(context, row);
1846
+ case 'market':
1847
+ return createMarketRow(context, row);
1576
1848
  case 'identity':
1577
1849
  case 'activity':
1578
1850
  case 'message':
@@ -1600,6 +1872,7 @@ export class NativeListWebEngine {
1600
1872
  actionAnchorInstanceId = String(++webNativeListInstanceCounter);
1601
1873
  actionAnchorCounter = 0;
1602
1874
  bindingEpochCounter = 0;
1875
+ marketLongPressFired = false;
1603
1876
  lastViewportWidth = -1;
1604
1877
  lastViewportHeight = -1;
1605
1878
  destroyed = false;
@@ -1643,6 +1916,10 @@ export class NativeListWebEngine {
1643
1916
  passive: true
1644
1917
  });
1645
1918
  this.root.addEventListener('click', this.handleClick);
1919
+ this.root.addEventListener('pointerdown', this.handleMarketPointerDown);
1920
+ this.root.addEventListener('pointermove', this.handleMarketPointerMove);
1921
+ this.root.addEventListener('pointerup', this.handleMarketPointerEnd);
1922
+ this.root.addEventListener('pointercancel', this.handleMarketPointerEnd);
1646
1923
  // OneKey patch: preserve web tooltip hover for section titles.
1647
1924
  this.root.addEventListener('pointerover', this.handleTitlePointerOver);
1648
1925
  this.root.addEventListener('pointerout', this.handleTitlePointerOut);
@@ -1698,12 +1975,31 @@ export class NativeListWebEngine {
1698
1975
  applyPatches(patches) {
1699
1976
  if (patches.length === 0) return;
1700
1977
  const next = applyRowPatches(this.snapshot, patches);
1701
- this.invalidateActionAnchor('snapshot');
1702
1978
  const selection = new Set(this.selectedKeys);
1703
1979
  patches.forEach(patch => {
1704
1980
  const selected = 'selected' in patch.changes ? patch.changes.selected : undefined;
1705
1981
  if (selected === true) selection.add(patch.key);else if (selected === false) selection.delete(patch.key);
1706
1982
  });
1983
+ if (webMarketImageBindDeltaForPatches(patches) === 0) {
1984
+ this.snapshot = next;
1985
+ this.rows = effectiveRows(next);
1986
+ this.selectedKeys = selection;
1987
+ patches.forEach(patch => {
1988
+ const index = this.rows.findIndex(row => row.key === patch.key);
1989
+ const row = this.rows[index];
1990
+ const element = this.mounted.get(index);
1991
+ if (row?.type === 'market' && element) {
1992
+ setMarketQuoteContent(element, row);
1993
+ element.dataset.renderSignature = webRowRenderSignature(row);
1994
+ }
1995
+ if (row?.type === 'market' && this.sticky.dataset.nativeListRowKey === row.key) {
1996
+ setMarketQuoteContent(this.sticky, row);
1997
+ this.sticky.dataset.renderSignature = webRowRenderSignature(row);
1998
+ }
1999
+ });
2000
+ return;
2001
+ }
2002
+ this.invalidateActionAnchor('snapshot');
1707
2003
  this.setSnapshot(next, selection);
1708
2004
  }
1709
2005
  reconcileSelection(keys) {
@@ -1827,6 +2123,11 @@ export class NativeListWebEngine {
1827
2123
  this.document.defaultView?.removeEventListener('resize', this.handleWindowResize);
1828
2124
  this.viewport.removeEventListener('scroll', this.handleScroll);
1829
2125
  this.root.removeEventListener('click', this.handleClick);
2126
+ this.cancelMarketPointer();
2127
+ this.root.removeEventListener('pointerdown', this.handleMarketPointerDown);
2128
+ this.root.removeEventListener('pointermove', this.handleMarketPointerMove);
2129
+ this.root.removeEventListener('pointerup', this.handleMarketPointerEnd);
2130
+ this.root.removeEventListener('pointercancel', this.handleMarketPointerEnd);
1830
2131
  this.root.removeEventListener('pointerover', this.handleTitlePointerOver);
1831
2132
  this.root.removeEventListener('pointerout', this.handleTitlePointerOut);
1832
2133
  this.root.removeEventListener('keydown', this.handleKeyDown);
@@ -1858,6 +2159,7 @@ export class NativeListWebEngine {
1858
2159
  this.avatarLeases.clear();
1859
2160
  }
1860
2161
  setSnapshot(snapshot, selectedKeys) {
2162
+ this.cancelMarketPointer();
1861
2163
  this.measuredWarningHeights.clear();
1862
2164
  this.snapshot = snapshot;
1863
2165
  this.rows = effectiveRows(snapshot);
@@ -2107,6 +2409,13 @@ export class NativeListWebEngine {
2107
2409
  }
2108
2410
  }
2109
2411
  element.replaceChildren(body);
2412
+ if (row.type === 'market' && row.diagnostics?.imageBindActionKey && body.querySelector('img')) {
2413
+ this.callbacks.onRowAction?.({
2414
+ rowKey: row.key,
2415
+ actionKey: row.diagnostics.imageBindActionKey,
2416
+ sectionKey: row.sectionKey
2417
+ });
2418
+ }
2110
2419
  }
2111
2420
  renderFooter() {
2112
2421
  const row = this.snapshot.fixedFooter;
@@ -2421,7 +2730,7 @@ export class NativeListWebEngine {
2421
2730
  }, row);
2422
2731
  return;
2423
2732
  }
2424
- const actionKey = row.type === 'action' ? row.actionKey : row.type === 'system' && row.variant === 'retry' ? row.actionKey : 'press';
2733
+ const actionKey = row.type === 'action' ? row.actionKey : row.type === 'system' && row.variant === 'retry' ? row.actionKey : row.type === 'market' && row.pressActionKey ? row.pressActionKey : 'press';
2425
2734
  if (rowElement && sourceElement) {
2426
2735
  const anchor = this.createActionAnchor(sourceElement, rowElement, 'row');
2427
2736
  this.callbacks.onRowAction?.({
@@ -2434,6 +2743,47 @@ export class NativeListWebEngine {
2434
2743
  this.emitRowAction(row, actionKey);
2435
2744
  }
2436
2745
  }
2746
+ cancelMarketPointer() {
2747
+ const state = this.marketPointer;
2748
+ if (!state) return;
2749
+ this.document.defaultView?.clearTimeout(state.timer);
2750
+ this.marketPointer = undefined;
2751
+ }
2752
+ handleMarketPointerDown = event => {
2753
+ if (event.button !== 0 || !(event.target instanceof Element)) return;
2754
+ this.marketLongPressFired = false;
2755
+ if (event.target.closest('[data-native-list-action]')) return;
2756
+ const rowElement = event.target.closest('[data-native-list-row-key]');
2757
+ const row = this.rowAtElement(rowElement);
2758
+ if (row?.type !== 'market' || row.disabled) return;
2759
+ this.cancelMarketPointer();
2760
+ if (row.pressInActionKey) this.emitRowAction(row, row.pressInActionKey, rowElement ?? undefined, rowElement ?? undefined);
2761
+ if (!row.longPressActionKey || !rowElement) return;
2762
+ markActionAnchorSource(rowElement, 'row');
2763
+ const timer = this.document.defaultView?.setTimeout(() => {
2764
+ const current = this.marketPointer;
2765
+ if (!current || current.pointerId !== event.pointerId) return;
2766
+ this.marketPointer = undefined;
2767
+ this.marketLongPressFired = true;
2768
+ this.emitRowAction(row, row.longPressActionKey, rowElement, rowElement);
2769
+ }, MARKET_LONG_PRESS_MS);
2770
+ if (timer === undefined) return;
2771
+ this.marketPointer = {
2772
+ pointerId: event.pointerId,
2773
+ rowKey: row.key,
2774
+ startX: event.clientX,
2775
+ startY: event.clientY,
2776
+ timer
2777
+ };
2778
+ };
2779
+ handleMarketPointerMove = event => {
2780
+ const state = this.marketPointer;
2781
+ if (!state || state.pointerId !== event.pointerId) return;
2782
+ if (Math.abs(event.clientX - state.startX) > MARKET_MOVE_CANCEL_PX || Math.abs(event.clientY - state.startY) > MARKET_MOVE_CANCEL_PX) this.cancelMarketPointer();
2783
+ };
2784
+ handleMarketPointerEnd = event => {
2785
+ if (this.marketPointer?.pointerId === event.pointerId) this.cancelMarketPointer();
2786
+ };
2437
2787
 
2438
2788
  // OneKey patch: hover opens the same anchored help action as native taps.
2439
2789
  handleTitlePointerOver = event => {
@@ -2456,7 +2806,8 @@ export class NativeListWebEngine {
2456
2806
  if (this.actionAnchor?.actionElement === action) this.invalidateActionAnchor('pointerLeave');
2457
2807
  };
2458
2808
  handleClick = event => {
2459
- if (Date.now() < this.suppressClickUntil) {
2809
+ if (this.marketLongPressFired || Date.now() < this.suppressClickUntil) {
2810
+ this.marketLongPressFired = false;
2460
2811
  event.preventDefault();
2461
2812
  event.stopPropagation();
2462
2813
  return;
@@ -2581,6 +2932,7 @@ export class NativeListWebEngine {
2581
2932
  });
2582
2933
  }
2583
2934
  handleScroll = () => {
2935
+ this.cancelMarketPointer();
2584
2936
  this.invalidateActionAnchor('scroll');
2585
2937
  this.scheduleFrame();
2586
2938
  };
@@ -28,6 +28,57 @@ export type BadgeModel = Readonly<{
28
28
  text: string;
29
29
  tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
30
30
  }>;
31
+ export type MarketTextStyle = Readonly<{
32
+ fontSize?: number;
33
+ fontWeight?: 'regular' | 'medium' | 'semibold' | 'bold';
34
+ color?: string;
35
+ lineHeight?: number;
36
+ lines?: 1 | 2;
37
+ alignment?: 'start' | 'center' | 'end';
38
+ }>;
39
+ export type MarketImageStyle = Readonly<{
40
+ width?: number;
41
+ height?: number;
42
+ shape?: 'circle' | 'rounded' | 'square';
43
+ cornerRadius?: number;
44
+ contentFit?: ImageContentFit;
45
+ }>;
46
+ export type MarketRowStyle = Readonly<{
47
+ horizontalPadding?: number;
48
+ verticalPadding?: number;
49
+ leadingGap?: number;
50
+ /** Space between title and subtitle; 0 by default, bounded to 0..16. */
51
+ lineGap?: number;
52
+ titleBadgeGap?: number;
53
+ trailingGap?: number;
54
+ image?: MarketImageStyle;
55
+ title?: MarketTextStyle;
56
+ subtitle?: MarketTextStyle;
57
+ price?: MarketTextStyle;
58
+ change?: MarketTextStyle;
59
+ changeWidth?: number;
60
+ changeHeight?: number;
61
+ changeCornerRadius?: number;
62
+ }>;
63
+ export type MarketBadgeModel = Readonly<{
64
+ key: string;
65
+ text?: string;
66
+ /** Finite native glyph set; currently only the community verified mark. */
67
+ iconName?: 'verified';
68
+ icon?: ImageSource;
69
+ tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
70
+ textColor?: string;
71
+ backgroundColor?: string;
72
+ actionKey?: string;
73
+ accessibilityLabel?: string;
74
+ }>;
75
+ export type MarketChangeModel = Readonly<{
76
+ text: string;
77
+ textSegments?: readonly ValueTextSegment[];
78
+ tone: 'positive' | 'negative' | 'neutral';
79
+ textColor?: string;
80
+ backgroundColor?: string;
81
+ }>;
31
82
  export type SelectorTextSegment = Readonly<{
32
83
  text: string;
33
84
  textSegments?: readonly ValueTextSegment[];
@@ -261,6 +312,26 @@ export type DataRow = RowBase & Readonly<{
261
312
  favorite?: boolean;
262
313
  favoriteActive?: boolean;
263
314
  }>;
315
+ /** Native Market quote row. All values are already localized/formatted strings. */
316
+ export type MarketRow = RowBase & Readonly<{
317
+ type: 'market';
318
+ variant: 'token' | 'stock' | 'perp';
319
+ leading: LeadingVisual;
320
+ title: string;
321
+ subtitle?: string;
322
+ subtitleSegments?: readonly ValueTextSegment[];
323
+ price: string;
324
+ priceSegments?: readonly ValueTextSegment[];
325
+ change: MarketChangeModel;
326
+ badges?: readonly MarketBadgeModel[];
327
+ pressActionKey?: string;
328
+ pressInActionKey?: string;
329
+ longPressActionKey?: string;
330
+ diagnostics?: Readonly<{
331
+ imageBindActionKey?: string;
332
+ }>;
333
+ style?: MarketRowStyle;
334
+ }>;
264
335
  export type MediaTileRow = RowBase & Readonly<{
265
336
  type: 'mediaTile';
266
337
  variant: 'gallery' | 'browserPreview';
@@ -335,10 +406,13 @@ export type ActionRow = RowBase & Readonly<{
335
406
  export type SystemRow = RowBase & (Readonly<{
336
407
  type: 'system';
337
408
  variant: 'loading';
409
+ presentation?: 'market';
410
+ loadingStyle?: 'skeleton' | 'spinner';
338
411
  message?: string;
339
412
  }> | Readonly<{
340
413
  type: 'system';
341
414
  variant: 'retry';
415
+ presentation?: 'market';
342
416
  message: string;
343
417
  actionKey: string;
344
418
  }> | Readonly<{
@@ -350,17 +424,19 @@ export type SystemRow = RowBase & (Readonly<{
350
424
  }> | Readonly<{
351
425
  type: 'system';
352
426
  variant: 'noMatch';
427
+ presentation?: 'market';
353
428
  message: string;
354
429
  }> | Readonly<{
355
430
  type: 'system';
356
431
  variant: 'end';
432
+ presentation?: 'market';
357
433
  message?: string;
358
434
  }> | Readonly<{
359
435
  type: 'system';
360
436
  variant: 'spacer';
361
437
  height: number;
362
438
  }>);
363
- export type RowModel = IdentityRow | WalletGroupRow | RailRow | ActivityRow | MessageRow | DataRow | MediaTileRow | MetricCardRow | SectionHeaderRow | ActionRow | SystemRow;
439
+ export type RowModel = IdentityRow | WalletGroupRow | RailRow | ActivityRow | MessageRow | DataRow | MarketRow | MediaTileRow | MetricCardRow | SectionHeaderRow | ActionRow | SystemRow;
364
440
  export type NativeListTheme = Readonly<{
365
441
  checkboxBackground?: string;
366
442
  checkboxBorder?: string;
@@ -445,6 +521,10 @@ export type RowPatch = Readonly<{
445
521
  type: 'dataRow';
446
522
  key: string;
447
523
  changes: Partial<Pick<DataRow, CommonPatchFields | 'leading' | 'columns' | 'checkbox' | 'index' | 'badge' | 'badges' | 'favorite' | 'favoriteActive'>>;
524
+ }> | Readonly<{
525
+ type: 'market';
526
+ key: string;
527
+ changes: Partial<Pick<MarketRow, CommonPatchFields | 'leading' | 'title' | 'subtitle' | 'subtitleSegments' | 'price' | 'priceSegments' | 'change' | 'badges' | 'pressActionKey' | 'pressInActionKey' | 'longPressActionKey' | 'diagnostics' | 'style'>>;
448
528
  }> | Readonly<{
449
529
  type: 'mediaTile';
450
530
  key: string;
@@ -470,7 +550,7 @@ export type RowPatch = Readonly<{
470
550
  message?: string;
471
551
  }>;
472
552
  }>;
473
- export type NativeListActionSource = 'row' | 'leadingAction' | 'trailingAccessory' | 'footerAction' | 'mediaClose';
553
+ export type NativeListActionSource = 'row' | 'marketBadge' | 'leadingAction' | 'trailingAccessory' | 'footerAction' | 'mediaClose';
474
554
  export type NativeListWindowRect = Readonly<{
475
555
  x: number;
476
556
  y: number;