gis-view-webgl2 0.1.0 → 0.1.2

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.
Files changed (87) hide show
  1. package/README.md +40 -8
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +1036 -249
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +194 -0
  7. package/docs/GisView.md +9 -8
  8. package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +2 -2
  9. package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +172 -0
  10. package/docs/api/index.html +545 -59
  11. package/docs/demos/BackendSyncDemo.vue +10 -8
  12. package/docs/demos/BasicDemo.vue +101 -101
  13. package/docs/demos/BatchAddDemo.vue +5 -5
  14. package/docs/demos/ChannelDemo.vue +617 -10
  15. package/docs/demos/ConnectDemo.vue +392 -0
  16. package/docs/demos/CopyMergeDemo.vue +980 -929
  17. package/docs/demos/DecorationDraftDemo.vue +1300 -0
  18. package/docs/demos/DemoShell.vue +569 -240
  19. package/docs/demos/DragDropDemo.vue +11 -11
  20. package/docs/demos/EffectsDemo.vue +6 -6
  21. package/docs/demos/EventDemo.vue +327 -327
  22. package/docs/demos/ExtensionDemo.vue +458 -0
  23. package/docs/demos/GisViewDemo.vue +61 -48
  24. package/docs/demos/GraphicDemo.vue +338 -0
  25. package/docs/demos/LabelDemo.vue +553 -497
  26. package/docs/demos/MinimapDemo.vue +358 -0
  27. package/docs/demos/PerfDemo.vue +29 -0
  28. package/docs/demos/SearchFilterDemo.vue +412 -0
  29. package/docs/demos/StyleDemo.vue +121 -121
  30. package/docs/demos/api-index.js +98 -77
  31. package/docs/demos/demos.js +159 -135
  32. package/docs/demos/shared/demoData.js +107 -107
  33. package/docs/demos/shared/demoStyle.css +249 -249
  34. package/docs/demos/shared/fakeBackend.js +93 -6
  35. package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +28 -8
  36. package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +5 -4
  37. package/docs/format/06-/345/257/274/345/205/245/345/257/274/345/207/272/344/270/216/346/240/207/345/207/206/346/226/207/346/241/243.md +8 -6
  38. package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +6 -6
  39. package/docs/guide/sample-document.html +4 -4
  40. package/docs/index.html +180 -9
  41. package/docs/internals/11-/350/246/206/347/233/226/345/261/202/346/240/207/347/255/276/344/270/216/346/217/220/347/244/272.md +1 -1
  42. package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +3 -2
  43. package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +16 -0
  44. package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -0
  45. package/docs/internals/17-/350/260/203/350/257/225/344/270/216/346/211/251/345/261/225/345/273/272/350/256/256.md +1 -1
  46. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +18 -4
  47. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +110 -25
  48. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +13 -8
  49. package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +111 -10
  50. package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +47 -9
  51. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +17 -15
  52. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +61 -11
  53. package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +74 -7
  54. package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +3 -2
  55. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +260 -3
  56. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +6 -5
  57. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +27 -14
  58. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +142 -49
  59. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +31 -29
  60. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +55 -39
  61. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +189 -64
  62. package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +4 -4
  63. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +42 -2
  64. package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +22 -6
  65. package/docs/playground/index.html +5 -5
  66. package/docs/steps/README.md +14 -10
  67. package/docs/steps/check-doc-truth.mjs +1 -1
  68. package/docs/steps/selftest-actions.html +10 -10
  69. package/docs/steps/selftest-data.html +96 -14
  70. package/docs/steps/selftest-decoration.html +577 -0
  71. package/docs/steps/selftest-demo.html +6 -5
  72. package/docs/steps/selftest-demos.html +2201 -1612
  73. package/docs/steps/selftest-docs.html +89 -16
  74. package/docs/steps/selftest-effects.html +5 -5
  75. package/docs/steps/selftest-features.html +757 -86
  76. package/docs/steps/selftest-full.html +583 -583
  77. package/docs/steps/selftest-labels.html +1034 -652
  78. package/docs/steps/selftest-perf.html +1899 -1844
  79. package/docs/steps/selftest-scale.html +117 -7
  80. package/docs/steps/selftest-select.html +24 -17
  81. package/docs/steps/step-10-full.js +1 -1
  82. package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +167 -17
  83. package/docs//346/225/260/346/215/256/346/240/274/345/274/217.md +1 -1
  84. package/package.json +43 -43
  85. package/docs/demos/HistoryDemo.vue +0 -230
  86. package/docs/demos/InterfaceDemo.vue +0 -309
  87. package/docs/demos/SampleDemo.vue +0 -283
@@ -121,12 +121,19 @@
121
121
  await Promise.race([api.ready, sleep(8000)])
122
122
  const meta = doc.querySelector('#content .meta')
123
123
  check('文档中心:首屏 README 渲染完(行数 / 体积读数 + 标题 + 目录)', Boolean(meta) && meta.textContent.includes('README') && doc.querySelectorAll('#content h1, #content h2').length >= 2 && doc.querySelectorAll('#toc nav a').length >= 3, meta ? meta.textContent.trim().slice(0, 72) : '8 s 还没读完')
124
+ const pathCards = [...doc.querySelectorAll('#content .doc-path')]
125
+ const pathDocs = pathCards.map(card => card.dataset.doc).filter(Boolean)
126
+ const apiPathCard = pathCards.find(card => card.textContent.includes('查方法与参数'))
127
+ check('文档中心:README 顶部有 4 条推荐阅读路径,3 篇文档加 API 页都指向真实目标', pathCards.length === 4 && pathDocs.length === 3 && pathDocs.every(file => files.includes(file)) && (apiPathCard?.getAttribute('href') ?? '').includes('api/index.html') && pathCards.every(card => card.querySelector('.doc-path__badge') && card.textContent.trim().length > 12), pathCards.map(card => card.querySelector('b')?.textContent?.trim()).join(' / '))
124
128
  check('文档中心:正文链接的解析(相对「当前那份文档」算,解析不到给 null)', api.resolve('docs/GisView.md', '../README.md') === 'GisView.md' && api.resolve('../从0到完整的实现.md', 'steps/README.md') === '从0到完整的实现.md' && api.resolve('../index.html', 'README.md') === null)
125
- const fixture = api.render('## 标题\n\n| A | B |\n| --- | --- |\n| 1 | 2 |\n\n```js\nconst a = 1\n```\n\n- 一级\n - 二级\n\n> 引用\n\n行内 `code` 与 [链接](GisView.md)\n')
126
- check('文档中心:markdown 渲染器(标题 / 表格 / 带语言代码块 / 两级列表 / 引用 / 行内代码 / md 链接 / 目录)', fixture.html.includes('<table>') && fixture.html.includes('data-lang="js"') && (fixture.html.match(/<ul>/g) ?? []).length >= 2 && fixture.html.includes('<blockquote>') && fixture.html.includes('<code>code</code>') && fixture.html.includes('data-doc="GisView.md"') && fixture.toc.length === 1, `${fixture.html.length} 字符 / ${fixture.toc.length} 条目录`)
129
+ const fixture = api.render('# 文档标题\n\n> 一句话摘要\n\n## 标题\n\n| A | B |\n| --- | --- |\n| 1 | 2 |\n\n```js\nconst a = 1\n```\n\n- 一级\n - 二级\n\n> 正文引用\n\n行内 `code` 与 [链接](GisView.md)\n')
130
+ const leadFixture = api.render('# 只有导语\n\n第一段就是全文导语。\n')
131
+ check('文档中心:markdown 渲染器(标题 / 表格 / 带语言代码块 / 两级列表 / 引用 / 行内代码 / md 链接 / 目录)', fixture.html.includes('<table>') && fixture.html.includes('data-lang="js"') && (fixture.html.match(/<ul>/g) ?? []).length >= 2 && fixture.html.includes('<blockquote class="doc-summary">') && fixture.html.includes('<blockquote><p>正文引用</p></blockquote>') && fixture.html.includes('<code>code</code>') && fixture.html.includes('data-doc="GisView.md"') && fixture.toc.length === 1, `${fixture.html.length} 字符 / ${fixture.toc.length} 条目录`)
132
+ check('文档中心:H1 后的摘要 / 首段分别套导语卡片与导语段落', leadFixture.html.includes('<p class="doc-lead">第一段就是全文导语。</p>'), leadFixture.html.slice(0, 120))
127
133
  await api.load('数据格式.md')
128
134
  const meta2 = doc.querySelector('#content .meta')
129
135
  const indexLinks = [...doc.querySelectorAll('#content a[data-doc]')]
136
+ check('文档中心:阅读路径只在 README 首屏出现,切到其他文档后移除', doc.querySelectorAll('#content .doc-path').length === 0)
130
137
  check('文档中心:索引页(数据格式.md)把 7 份子文档都链上了,每条都解析到清单里的真文件', Boolean(meta2) && meta2.textContent.includes('数据格式.md') && indexLinks.length >= 7 && indexLinks.every(link => files.includes(api.resolve('数据格式.md', link.dataset.doc))), meta2 ? `${indexLinks.length} 条子文档链接 / ${doc.querySelectorAll('#toc nav a').length} 条目录` : '')
131
138
  await api.load('format/07-事件载荷与示例数据.md')
132
139
  check('文档中心:子文档(format/07)表格与目录都渲染出来(这一份没有围栏代码,全是缩进续行)', doc.querySelectorAll('#content table').length >= 4 && doc.querySelectorAll('#toc nav a').length >= 5, `${doc.querySelectorAll('#content table').length} 表 / ${doc.querySelectorAll('#toc nav a').length} 条目录`)
@@ -373,23 +380,25 @@
373
380
  doc.documentElement.classList.contains('theme-dark') === Boolean(docsFrame.contentWindow.matchMedia('(prefers-color-scheme: dark)').matches))
374
381
  const progressEl = doc.querySelector('#progress')
375
382
  const backtopEl = doc.querySelector('#backtop')
383
+ const backtopPercentEl = doc.querySelector('#backtop-percent')
376
384
  docsFrame.contentWindow.scrollTo(0, 4000)
377
385
  api.syncScrollReadouts()
378
386
  const scrolledWidth = progressEl.style.width
379
387
  const backtopOn = backtopEl.classList.contains('on')
388
+ const scrolledPercent = backtopPercentEl?.textContent ?? ''
380
389
  docsFrame.contentWindow.scrollTo(0, 0)
381
390
  api.syncScrollReadouts()
382
391
  check('文档中心:顶部进度条跟着滚动长、「回到顶部」滚下去才出现(回顶后收回去)',
383
392
  scrolledWidth !== '' && scrolledWidth !== '0%' && scrolledWidth !== '0.00%' && backtopOn &&
384
- parseFloat(progressEl.style.width || '0') === 0 && !backtopEl.classList.contains('on'),
385
- `滚下去 ${scrolledWidth} / 回顶 ${progressEl.style.width} · 按钮 ${backtopOn ? '在' : '不在'}`)
393
+ parseInt(scrolledPercent, 10) > 0 && parseFloat(progressEl.style.width || '0') === 0 && backtopPercentEl?.textContent === '0%' && !backtopEl.classList.contains('on'),
394
+ `滚下去 ${scrolledWidth} / ${scrolledPercent} · 回顶 ${progressEl.style.width} · 数字 ${backtopPercentEl?.textContent} · 按钮 ${backtopOn ? '在' : '不在'}`)
386
395
  /* ---- 这一轮加的:文档页的观感(侧栏计数 / 元信息胶囊 / 代码块头部 / 空态 / 「/」快捷键) ---- */
387
396
  const counts = [doc.querySelector('#doc-count')?.textContent, doc.querySelector('#page-count')?.textContent]
388
397
  check('文档中心:侧栏两个分组标题上挂着计数(数得跟清单一样)', counts[0] === String(api.docs.length) && counts[1] === String(api.pages.length), counts.join(' / '))
389
398
  await api.load('GisView.md')
390
399
  const chips = [...doc.querySelectorAll('#content .meta > *')]
391
- check('文档中心:正文开头那行元信息是几枚胶囊(文件 / 行数 + 体积 / 小节数 / 代码块数 / 看原文)',
392
- chips.length === 5 && chips[0].textContent === 'GisView.md' && /\d+ 行 \/ [\d.]+ KB/.test(chips[1].textContent) && /\d+ 个小节/.test(chips[2].textContent) && /\d+ 段代码/.test(chips[3].textContent) && chips[4].textContent === '看原文',
400
+ check('文档中心:正文开头那行元信息是几枚胶囊(文件 / 行数 + 体积 / 阅读时长 / 小节数 / 代码块数 / 看原文)',
401
+ chips.length === 6 && chips[0].textContent === 'GisView.md' && /\d+ 行 \/ [\d.]+ KB/.test(chips[1].textContent) && /^约 \d+ 分钟$/.test(chips[2].textContent) && /\d+ 个小节/.test(chips[3].textContent) && /\d+ 段代码/.test(chips[4].textContent) && chips[5].textContent === '看原文',
393
402
  chips.map(node => node.textContent).join(' | ').slice(0, 120))
394
403
  check('文档中心:语言徽标与「复制」都收在代码块头部那一条里(一块只有一个按钮)',
395
404
  [...doc.querySelectorAll('#content .code')].every(block => block.querySelector('.code-head > button') && block.querySelectorAll('button').length === 1),
@@ -409,6 +418,39 @@
409
418
  searchInputEl.value = ''
410
419
  searchInputEl.dispatchEvent(new Event('input', { bubbles: true }))
411
420
  await api.load('README.md')
421
+ /* 390px 视口回归:窄屏时侧栏排在正文上方,首屏不能被 content.scrollIntoView 带过目录 */
422
+ const mobileFrame = document.createElement('iframe')
423
+ mobileFrame.title = '文档中心 390px'
424
+ mobileFrame.style.width = '390px'
425
+ mobileFrame.style.height = '760px'
426
+ mobileFrame.src = '../index.html?selftest=390'
427
+ document.body.append(mobileFrame)
428
+ const mobileReady = await waitFor(() => {
429
+ const win = mobileFrame.contentWindow
430
+ return win?.__gisDocs && mobileFrame.contentDocument?.querySelector('#content .meta') ? win : null
431
+ })
432
+ await sleep(350)
433
+ const mobileWin = mobileReady
434
+ const mobileDoc = mobileFrame.contentDocument
435
+ const initialScroll = mobileWin.scrollY
436
+ const initialOverflow = mobileDoc.documentElement.scrollWidth > mobileDoc.documentElement.clientWidth + 1
437
+ check('文档中心:390px 首屏停在页面顶部(不被正文 scrollIntoView 带过目录)', initialScroll === 0 && mobileWin.history.scrollRestoration === 'manual', `scrollY=${initialScroll} / ${mobileWin.history.scrollRestoration}`)
438
+ check('文档中心:390px 首屏无横向溢出', !initialOverflow && mobileWin.innerWidth < 941, `视口 ${mobileWin.innerWidth}px / ${mobileDoc.documentElement.scrollWidth}px`)
439
+ const deepUrl = new URL('../index.html?selftest=deep', location.href)
440
+ deepUrl.hash = `${encodeURIComponent('manual/15-导出与集成.md')}~${encodeURIComponent('插件')}`
441
+ const mobileReloaded = new Promise(resolve => mobileFrame.addEventListener('load', resolve, { once: true }))
442
+ mobileFrame.src = deepUrl.href
443
+ await mobileReloaded
444
+ const deepReady = await waitFor(() => {
445
+ const win = mobileFrame.contentWindow
446
+ return win?.__gisDocs && mobileFrame.contentDocument?.querySelector('[data-slug="插件"]') ? win : null
447
+ })
448
+ await sleep(350)
449
+ const deepDoc = mobileFrame.contentDocument
450
+ const deepTarget = deepDoc.querySelector('[data-slug="插件"]')
451
+ const deepTop = deepTarget?.getBoundingClientRect().top ?? -1
452
+ check('文档中心:390px 深链仍落到对应小节(顶栏下 72px)', deepReady.__gisDocs.current() === 'manual/15-导出与集成.md' && deepReady.scrollY > 0 && deepTop >= 60 && deepTop <= 90, `${deepReady.__gisDocs.current()} / scrollY=${Math.round(deepReady.scrollY)} / top=${Math.round(deepTop)}`)
453
+ mobileFrame.remove()
412
454
  }
413
455
  /* ---------------- 图解页:docs/guide/sample-document.html ---------------- */
414
456
  const guideFrame = document.querySelector('#guide-frame')
@@ -476,21 +518,39 @@
476
518
  if (check('API 速查:挂上了 __gisApi 钩子(数据来自 api-index.js,没钩子多半是 file:// 打开的)', Boolean(apiHook))) {
477
519
  const apiDoc = apiFrame.contentDocument
478
520
  const summary = apiHook.summary
479
- check('API 速查:清单来自 api-index(243 个成员 / 87 个 options / 14 个分组)',
480
- summary.members === 243 && summary.options === 87 && summary.sections === 14,
521
+ check('API 速查:清单来自 api-index(249 个成员 / 92 个 options / 16 个分组)',
522
+ summary.members === 249 && summary.options === 92 && summary.sections === 16,
481
523
  JSON.stringify(summary))
482
- check('API 速查:分组都渲染出来(14 组 + options 一组)',
483
- apiDoc.querySelectorAll('.api-group').length === 15 && apiDoc.querySelectorAll('.api-group > h2 b').length === 15,
524
+ check('API 速查:左侧分组有当前项,滚动 / 点选时会跟着亮', apiHook.activeGroup() !== null, String(apiHook.activeGroup()))
525
+ check('API 速查:分组都渲染出来(16 组 + options 一组)',
526
+ apiDoc.querySelectorAll('.api-group').length === 17 && apiDoc.querySelectorAll('.api-group > h2 b').length === 17,
484
527
  `${apiDoc.querySelectorAll('.api-group').length} 组`)
485
528
  check('API 速查:成员卡片全在(方法与属性按清单来)',
486
529
  apiDoc.querySelectorAll('.api-member').length === summary.members && apiDoc.querySelectorAll('.api-member[data-kind="property"]').length === summary.properties,
487
530
  `${apiDoc.querySelectorAll('.api-member').length} 张卡片 / 属性 ${apiDoc.querySelectorAll('.api-member[data-kind="property"]').length} 张`)
488
531
  const noExample = [...apiDoc.querySelectorAll('.api-member')].filter(node => Number(node.dataset.examples) < 1)
489
532
  check('API 速查:每个成员都带至少一段示例(不出现空卡片)', noExample.length === 0,
490
- noExample.length ? noExample.map(node => node.dataset.name).join(' / ') : '243 张卡片都有示例')
491
- check('API 速查:87 个 options 都列全(名字 / 类型 / 默认值 / 说明四件套)',
533
+ noExample.length ? noExample.map(node => node.dataset.name).join(' / ') : '249 张卡片都有示例')
534
+ check('API 速查:92 个 options 都列全(名字 / 类型 / 默认值 / 说明四件套)',
492
535
  apiDoc.querySelectorAll('.api-option').length === summary.options && [...apiDoc.querySelectorAll('.api-option')].every(node => node.querySelector('.name code') && node.querySelector('.type') && node.querySelector('.def') && node.querySelector('.doc')),
493
536
  `${apiDoc.querySelectorAll('.api-option').length} 个`)
537
+ const statCards = [...apiDoc.querySelectorAll('#stats .api-stat')]
538
+ const optionStat = statCards.find(node => node.dataset.stat === 'options')
539
+ check('API 速查:首屏统计卡为六张,options 卡读数与清单一致',
540
+ statCards.length === 6 && statCards.map(node => node.dataset.stat).join(',') === 'members,methods,properties,options,sections,examples' && (optionStat?.textContent ?? '').includes(String(summary.options)),
541
+ `${statCards.length} 张 / ${optionStat?.textContent ?? '缺 options 卡'}`)
542
+ const optionHead = apiDoc.querySelector('.option-table > .option-head')
543
+ check('API 速查:options 用四列表头(字段 / 类型 / 默认值 / 说明)',
544
+ optionHead?.children.length === 4 && [...optionHead.children].map(node => node.textContent).join(',') === '字段,类型,默认值,说明',
545
+ `${optionHead?.children.length ?? 0} 列`)
546
+ check('API 速查:成员卡片都带参数 / 返回签名网格',
547
+ apiDoc.querySelectorAll('.api-member .signature-grid').length === summary.members && [...apiDoc.querySelectorAll('.api-member .signature-grid')].every(node => node.children.length === 2),
548
+ `${apiDoc.querySelectorAll('.api-member .signature-grid').length} 张`)
549
+ const memberExamples = [...apiDoc.querySelectorAll('.api-member .example')]
550
+ check('API 速查:示例改为可折叠 details,首段默认展开且仍保留运行按钮',
551
+ memberExamples.length > 0 && memberExamples.every(node => node.tagName === 'DETAILS') && memberExamples[0]?.open === true &&
552
+ apiDoc.querySelectorAll('.api-member .example .try-code').length === memberExamples.length,
553
+ `${memberExamples.length} 段 / 首段展开 ${memberExamples[0]?.open === true}`)
494
554
  check('API 速查:代码块带高亮,每段示例都有复制按钮',
495
555
  apiDoc.querySelectorAll('.example pre code .tok-fn').length > 0 && [...apiDoc.querySelectorAll('.example')].every(node => node.querySelector('button')),
496
556
  `${apiDoc.querySelectorAll('.example').length} 段示例`)
@@ -529,6 +589,18 @@
529
589
  apiDoc.querySelectorAll('.api-member .try-code').length === apiDoc.querySelectorAll('.api-member .example').length &&
530
590
  apiOpens.length === 1 && apiOpens[0].includes('playground/index.html?from=api') && stashed.includes('updateLine'),
531
591
  `${apiDoc.querySelectorAll('.api-member .try-code').length} 个按钮 / ${apiOpens.join(',')}`)
592
+ const themeBefore = apiHook.theme()
593
+ apiHook.setTheme('dark')
594
+ await settle(apiFrame.contentWindow, 1)
595
+ const darkThemeApplied = apiDoc.documentElement.classList.contains('theme-dark') && apiDoc.querySelector('#theme-btn')?.textContent === '主题:深色'
596
+ const storedTheme = JSON.parse(apiFrame.contentWindow.localStorage.getItem('gisDocs.reading') ?? 'null')?.theme
597
+ apiHook.setTheme('light')
598
+ await settle(apiFrame.contentWindow, 1)
599
+ const lightThemeApplied = !apiDoc.documentElement.classList.contains('theme-dark') && apiDoc.querySelector('#theme-btn')?.textContent === '主题:浅色'
600
+ apiHook.setTheme(themeBefore)
601
+ check('API 速查:深 / 浅主题切换生效并写进文档中心共用的阅读设置',
602
+ darkThemeApplied && lightThemeApplied && storedTheme === 'dark',
603
+ `深色 ${darkThemeApplied} / 浅色 ${lightThemeApplied} / 存储 ${storedTheme}`)
532
604
  }
533
605
  /* ---------------- 调试演示页:docs/playground/index.html ---------------- */
534
606
  const playFrame = document.querySelector('#playground-frame')
@@ -540,7 +612,7 @@
540
612
  Boolean(playView) && playView.getData().points.length === 3 && playView.getData().lines.length === 3 && playView.getChannels().length === 2,
541
613
  `${playView?.getData().points.length} 点 / ${playView?.getChannels().length} 组`)
542
614
  check('调试演示页:示例下拉有货,编辑器默认载入欢迎示例', playHook.sampleNames().length >= 5 && (playHook.getCode() ?? '').includes('view.'), `${playHook.sampleNames().length} 个示例`)
543
- await playHook.run('view.setPointColor(\'a\', \'#123456\')\nview.getData().points.length')
615
+ await playHook.run('view.updatePoint(\'a\', { color: \'#123456\' })\nview.getData().points.length')
544
616
  await settle(playFrame.contentWindow, 1)
545
617
  const playOut = playDoc.querySelector('#out').textContent
546
618
  check('调试演示页:run(code) 改到真实视图,最后一行返回值打在输出区',
@@ -550,12 +622,13 @@
550
622
  await settle(playFrame.contentWindow, 1)
551
623
  const playErr = playDoc.querySelector('#out').textContent
552
624
  check('调试演示页:代码报错打在输出区(红字 + 不炸整页)', playErr.includes('noSuchMethod') && playDoc.querySelector('#out .err') !== null, playErr.split('\n')[0])
625
+ const playCanvasCount = playDoc.querySelectorAll('#map canvas').length
553
626
  await playHook.run('createGisView(\'#map\', {})\nview.getData().points.length')
554
627
  await settle(playFrame.contentWindow, 1)
555
628
  check('调试演示页:示例里的 createGisView(\'#map\') 沿用右边这份视图(不会新开一块画布)',
556
- playDoc.querySelectorAll('#map canvas').length === 1 && (playDoc.querySelector('#out').textContent ?? '').includes('3'),
557
- `画布 ${playDoc.querySelectorAll('#map canvas').length} 块`)
558
- playHook.setCode('view.setPointColor(\'b\', \'#00aa88\')')
629
+ playDoc.querySelectorAll('#map canvas').length === playCanvasCount && (playDoc.querySelector('#out').textContent ?? '').includes('3'),
630
+ `画布 ${playCanvasCount} → ${playDoc.querySelectorAll('#map canvas').length} 块`)
631
+ playHook.setCode('view.updatePoint(\'b\', { color: \'#00aa88\' })')
559
632
  check('调试演示页:setCode 换编辑器内容(深链 #code= 与 API 页带过来的代码都走它)', playHook.getCode().includes('00aa88'))
560
633
  playHook.reset()
561
634
  await settle(playFrame.contentWindow, 1)
@@ -284,19 +284,19 @@
284
284
  JSON.stringify({ point: stats.pointEffectCount, line: stats.lineEffectCount }))
285
285
  const still = view.getEffects()
286
286
  check('关掉一层之后 getEffects 读得出来(point 为 null)', still.point === null && still.line.period === 1, JSON.stringify({ point: still.point, period: still.line?.period }))
287
- view.setEffects(false)
287
+ view.updateOptions({ effects: false })
288
288
  await advance(60, 2)
289
289
  stats = view.getStats()
290
- check('setEffects(false) 一处关掉:动点与涟漪都收掉、动画停下',
290
+ check('effects: false 一处关掉:动点与涟漪都收掉、动画停下',
291
291
  stats.lineEffectCount === 0 && stats.pointEffectCount === 0 && stats.animating === false && view.getLineEffects().length === 0,
292
292
  JSON.stringify({ line: stats.lineEffectCount, point: stats.pointEffectCount, animating: stats.animating }))
293
293
  check('总开关关着时 getEffects.enabled 为 false', view.getEffects().enabled === false)
294
- view.setEffects({ line: { period: 1 }, point: { radius: 18 } })
294
+ view.updateOptions({ effects: { line: { period: 1 }, point: { radius: 18 } } })
295
295
  await advance(60, 2)
296
296
  stats = view.getStats()
297
297
  check('再次打开:动点与涟漪都回来', stats.lineEffectCount === 1 && stats.pointEffectCount === 3,
298
298
  JSON.stringify({ line: stats.lineEffectCount, point: stats.pointEffectCount }))
299
- check('getOptions 与 setEffects 一致(配置记进了 current)', view.getOptions().effects?.line?.period === 1, JSON.stringify(view.getOptions().effects?.line?.period))
299
+ check('getOptions 与 updateOptions({ effects }) 一致(配置记进了 current)', view.getOptions().effects?.line?.period === 1, JSON.stringify(view.getOptions().effects?.line?.period))
300
300
 
301
301
  /* ---------------- 引擎:全局什么都不配,只在数据里逐条配 ---------------- */
302
302
  // 需求里的第二种写法:不动全局缺省,写在哪条上哪条就动(全局没配不代表关掉)
@@ -317,7 +317,7 @@
317
317
  const soloEffects = solo.getEffects()
318
318
  check('这一份的 getEffects:总开关为真、两层缺省都是 null(画什么全看逐要素)',
319
319
  soloEffects.enabled === true && soloEffects.line === null && soloEffects.point === null, JSON.stringify(soloEffects))
320
- solo.setEffects(false)
320
+ solo.updateOptions({ effects: false })
321
321
  await advance(60, 2)
322
322
  check('显式 effects: false 连逐要素配的一起关掉(一处关掉的语义)',
323
323
  solo.getStats().lineEffectCount === 0 && solo.getStats().pointEffectCount === 0 && solo.getStats().animating === false,