@metweave/leaflet 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["bad"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @metweave/leaflet — Leaflet 适配器:报文卡片上图的桥接层。\n * The Leaflet adapter: puts metweave report cards onto a Leaflet map.\n *\n * v0.1 唯一地图适配器;更多地图库适配(MapLibre 等)在路线图上。\n * 兼容契约 = Leaflet overlay 能力:底图瓦片是宿主侧一行 L.tileLayer 配置,本包不做任何绑定,\n * 也不推荐或代理任何底图服务——底图由使用者按自己的授权自行选择。\n */\nimport type * as Leaflet from \"leaflet\";\nimport type { CloudElement, MetarReport, WeatherGroup } from \"@metweave/core\";\nimport { toValues } from \"@metweave/core\";\nimport { renderCard } from \"@metweave/render\";\n\n/**\n * leaflet 惰性装载:import 本包时不再静态加载 leaflet——peer 库是浏览器专属实现,\n * 顶层静态 import 会让 Node/SSR 的模块图在求值期即崩(window is not defined,\n * 2026-09-16 五方实测评测实锤:ESM/CJS 双格式 import 均炸)。改为首次 addMetarLayer\n * 调用时动态 import 并缓存;类型面仍由顶层 import type 提供,宿主类型检查零影响。\n */\nlet leafletModule: Promise<typeof import(\"leaflet\")> | undefined;\nconst loadLeaflet = (): Promise<typeof import(\"leaflet\")> => (leafletModule ??= import(\"leaflet\"));\n\n/**\n * One station on the map: parsed report IR + WGS-84 position + optional tooltip title.\n * 地图上的一个站点:已解析报文 IR + WGS-84 坐标 + 可选悬浮标题。\n */\nexport interface MetarLayerItem {\n /** 已解析的报文 IR */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon];用 GCJ-02 底图时的偏移由宿主决定是否校正) */\n position: [number, number];\n /** 站点提示(tooltip 文案,缺省用 IR 站名)。按纯文本处理——不解析 HTML,宿主自传任意串无注入面 */\n title?: string;\n}\n\n/**\n * Options for addMetarLayer: popup behavior, pass-through renderCard options, and the condition-color dot mode.\n * addMetarLayer 的选项:弹窗行为、透传 renderCard 的选项、条件色圆点模式。\n */\nexport interface AddMetarLayerOptions {\n /** 显示语言速记:等价 `card.locale`,同时决定 tooltip 摘要/档位词与弹窗卡片语言;\n * card.locale 显式传入时以其为准。未知选项运行时抛错(拼写错误不静默) */\n locale?: \"zh\" | \"en\";\n /** 点击站点时以弹窗展示报文卡片(缺省开启) */\n popup?: boolean;\n /** 传给 renderCard 的选项 */\n card?: Parameters<typeof renderCard>[1];\n /** marker 按四档气象条件着色(unknown/poor/caution/good 圆点替代默认图钉;缺省 false 保持现状)。\n * 判据为本库自拟的扫视启发式(初稿待审,见 conditionOf 注释)——不对应任何官方飞行天气分类,\n * 不得用作运行判据;\n * 开启时 tooltip 同时追加一行要素摘要(能见度/天气/最差云;NIL 与关键组全缺测站显示「缺报/数据缺测」),\n * 圆点 aria-label 追加档位词(zh「天气好/差…」/ en「Weather …」,语言随 card.locale) */\n conditionColors?: boolean;\n}\n\n/** addMetarLayer 的合法选项键(运行时校验用——拼错的选项键静默忽略违反本库不静默纪律) */\nconst ADD_METAR_LAYER_OPTION_KEYS: ReadonlySet<string> = new Set([\n \"locale\",\n \"popup\",\n \"card\",\n \"conditionColors\",\n]);\n\n/**\n * tooltip 纯文本承载:Leaflet 对 string 内容走 innerHTML——宿主自传的 title 是任意串,\n * 经由 textContent 装载(非 HTML 路径)杜绝标记注入;文案渲染不受影响。\n */\nfunction textCarrier(text: string): HTMLElement {\n const el = document.createElement(\"span\");\n el.textContent = text;\n return el;\n}\n\n/** HTML 属性上下文转义(divIcon 的 html 字符串构造用——title/站名不可信面) */\nconst escapeHtml = (text: string): string =>\n text.replace(/[&<>\"']/g, (ch) => {\n switch (ch) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n\n/** 四档条件色(与卡片 mw-danger/mw-caution 同族色相:灰=不明、红=差、琥珀=注意、绿=好) */\nconst TIER_COLORS: Record<ConditionTier, string> = {\n unknown: \"#8a94a0\",\n poor: \"#d05656\",\n caution: \"#e0a13c\",\n good: \"#3aa657\",\n};\n\ntype ConditionTier = \"unknown\" | \"poor\" | \"caution\" | \"good\";\n\n/** 档位可读名(aria-label 追加词——a11y 1.4.1:档位信息不只靠颜色传达) */\nconst TIER_WORDS: Record<\"zh\" | \"en\", Record<ConditionTier, string>> = {\n zh: { unknown: \"天气不明\", poor: \"天气差\", caution: \"天气注意\", good: \"天气好\" },\n en: {\n unknown: \"Weather unknown\",\n poor: \"Weather poor\",\n caution: \"Weather caution\",\n good: \"Weather good\",\n },\n};\n\n/** 降水类现象(caution 判据用,与 WMO 4678 降水族对应——RA/SN/SG/PL/GS/IC/DZ/UP) */\nconst PRECIP_PHENOMENA: ReadonlySet<string> = new Set([\n \"RA\",\n \"SN\",\n \"SG\",\n \"PL\",\n \"GS\",\n \"IC\",\n \"DZ\",\n \"UP\",\n]);\n\n/** 风速折米/秒(阵风判据统一单位:kt ×0.514444、kmh ÷3.6、mps ×1) */\nconst toMps = (value: number, unit: \"kt\" | \"mps\" | \"kmh\"): number =>\n unit === \"kt\" ? value * 0.514444 : unit === \"kmh\" ? value / 3.6 : value;\n\n/**\n * 四档气象条件分级(判据本库自拟、初稿待审——显示层扫视启发式):\n * 阈值由本库拟定,**不对应也不代表任何官方飞行天气分类;本库不提供飞行规则判定**(本期无此功能)。\n * 仅供「一眼扫视哪些站值得注意」,不得作为任何运行判据:\n * - unknown(灰)= NIL(台站无观测)或关键组全缺测(能见度与云均缺测且天气缺测/无——按可得要素无从判读)\n * - poor(红)= 能见度 < 1500 m,或 BKN/OVC 云层(含垂直能见度)云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)\n * 或现象含 GR/VA,或冻降水(FZ 描述符族,冻雨/冻毛毛雨),或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭\n * - caution(琥珀)= 能见度 1500–5000 m(能见度分档取国内通行 1500/5000 m 口径),或 BKN/OVC 云底 1000–3000 ft,或任何降水族(RA/SN 等),\n * 或 FZ 描述符以外的结冰现象,或阵风 15–25 m/s\n * - good(绿)= 其余(含 CAVOK)\n * 缺测要素不参与限制(按可得要素判,见 conditionOf 内 unknown 判据的例外);阈值细则随口径审定后修订。\n */\nfunction conditionOf(report: MetarReport): ConditionTier {\n if (report.nil === true) return \"unknown\";\n const v = toValues(report);\n // 关键组全缺测:能见度与云均缺测(云组每个体均为全缺测形态)且天气缺测/无——判读无从下手,灰而非绿\n const visMissing = report.visibility?.kind === \"missing\";\n const elements = v.clouds?.elements ?? [];\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone) return \"unknown\";\n\n // —— poor 判据(任一命中即红)\n const vis = v.visibility;\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 1500) return \"poor\";\n }\n const ceilings = elements\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .map((e) => e.heightFt.value ?? Number.POSITIVE_INFINITY);\n for (const e of elements) {\n if (e.kind === \"vertical-visibility\")\n ceilings.push(e.heightFt.value ?? Number.POSITIVE_INFINITY);\n }\n const ceiling = ceilings.length > 0 ? Math.min(...ceilings) : Number.POSITIVE_INFINITY;\n if (ceiling < 1000) return \"poor\";\n for (const g of v.weather ?? []) {\n const thunderstorm = g.descriptor === \"TS\"; // TS 族:任何雷暴(含 VCTS 邻近雷暴)\n const hailOrAsh = g.phenomena.includes(\"GR\") || g.phenomena.includes(\"VA\");\n const freezing = g.descriptor === \"FZ\"; // 冻降水族(FZRA/FZDZ 等)——危害与雷暴同级,2026-09-22 运行视角评审升红\n const heavyPrecip =\n g.intensity === \"+\" &&\n (g.descriptor === \"SH\" || g.phenomena.some((p) => PRECIP_PHENOMENA.has(p)));\n if (thunderstorm || hailOrAsh || freezing || heavyPrecip) return \"poor\";\n }\n const gust = v.wind?.gust;\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 25) return \"poor\";\n const convective = elements.some(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n if (convective) return \"poor\";\n if (v.runwayStates.some((st) => st.closed === true)) return \"poor\";\n\n // —— caution 判据(任一命中即琥珀)\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 5000) return \"caution\";\n }\n if (ceiling < 3000) return \"caution\";\n for (const g of v.weather ?? []) {\n const precip = g.phenomena.some((p) => PRECIP_PHENOMENA.has(p));\n if (precip) return \"caution\";\n }\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 15) return \"caution\";\n return \"good\";\n}\n\n/** 天气组显示码:span 在位取原码,缺席由 IR 重建(摘要行的要素原样口径) */\nfunction weatherCode(report: MetarReport, g: WeatherGroup): string {\n return g.span === undefined\n ? `${g.proximity ? \"VC\" : \"\"}${g.intensity ?? \"\"}${g.descriptor ?? \"\"}${g.phenomena.join(\"\")}`\n : report.raw.slice(g.span.start, g.span.end);\n}\n\n/** 云层显示码:span 在位取原码,缺席由 IR 重建(缺测位还原为 ///) */\nfunction cloudCode(report: MetarReport, layer: CloudElement): string {\n if (layer.span === undefined) {\n if (layer.kind === \"vertical-visibility\") {\n return `VV${layer.heightFt.value === null ? \"///\" : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\")}`;\n }\n const height =\n layer.heightFt.value === null\n ? \"///\"\n : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\");\n return `${layer.amount ?? \"///\"}${height}${layer.convective ?? \"\"}`;\n }\n return report.raw.slice(layer.span.start, layer.span.end);\n}\n\n/**\n * tooltip 第二行要素摘要(扫视初筛):`2500m +TSRA BKN030CB` 式——\n * 能见度 / 最显著天气(优先 TS/GR/+ 强度族)/ 最差云(对流云优先,否则最低 BKN/OVC,VV 兜底)。\n * NIL 站显示「缺报(NIL)」、关键组全缺测站显示「数据缺测」(与 conditionOf 的 unknown 判据同款口径)。\n */\nfunction summarizeReport(report: MetarReport, locale: \"zh\" | \"en\"): string {\n if (report.nil === true) return locale === \"en\" ? \"No report (NIL)\" : \"缺报(NIL)\";\n if (report.cavok) return \"CAVOK\";\n const v = toValues(report);\n // 关键组全缺测(能见度与云均缺测且天气缺测/无):要素摘要无从拼起,显示缺测占位而非空行\n const elements = v.clouds?.elements ?? [];\n const visMissing = report.visibility?.kind === \"missing\";\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone)\n return locale === \"en\" ? \"Data missing\" : \"数据缺测\";\n const parts: string[] = [];\n const vis = v.visibility;\n if (vis !== undefined) {\n parts.push(\n vis.unit === \"m\"\n ? vis.exact\n ? `${vis.value} m`\n : \"≥10 km\"\n : vis.beyond === \"below\"\n ? `<${vis.value} SM`\n : vis.beyond === \"above\"\n ? `>${vis.value} SM`\n : `${vis.value} SM`,\n );\n }\n const weather = v.weather ?? [];\n const significant =\n weather.find(\n (g) => g.descriptor === \"TS\" || g.phenomena.includes(\"GR\") || g.intensity === \"+\",\n ) ?? weather[0];\n if (significant !== undefined) parts.push(weatherCode(report, significant));\n const layers = v.clouds?.elements ?? [];\n const convective = layers.find(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n const ceilingLayer = layers\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .reduce<Extract<CloudElement, { kind: \"layer\" }> | undefined>(\n (lowest, e) =>\n lowest === undefined ||\n (e.heightFt.value ?? Number.POSITIVE_INFINITY) <\n (lowest.heightFt.value ?? Number.POSITIVE_INFINITY)\n ? e\n : lowest,\n undefined,\n );\n const vertical = layers.find((e) => e.kind === \"vertical-visibility\");\n const worstCloud = convective ?? ceilingLayer ?? vertical;\n if (worstCloud !== undefined) parts.push(cloudCode(report, worstCloud));\n else if (v.clouds?.clear !== undefined) parts.push(v.clouds.clear.code);\n return parts.join(\" \");\n}\n\n/** tooltip 内容节点:标题行 + 要素摘要行(两行皆纯文本装载;locale 决定摘要占位词语言) */\nfunction tooltipContent(\n item: MetarLayerItem,\n withSummary: boolean,\n locale: \"zh\" | \"en\",\n): HTMLElement {\n const container = document.createElement(\"span\");\n container.append(textCarrier(item.title ?? item.report.station));\n if (withSummary) {\n container.append(\n document.createElement(\"br\"),\n textCarrier(summarizeReport(item.report, locale)),\n );\n }\n return container;\n}\n\n/** Escape 关闭已开弹窗的监听只挂一次/地图(多图层叠加不重复绑定) */\nconst escapeBoundMaps = new WeakSet<Leaflet.Map>();\n\n/**\n * Put a set of report stations onto a Leaflet map: markers + tooltips + card popups. Returns a removable layer group.\n * 把一组报文站点挂上地图:标记 + tooltip + 卡片弹窗。返回可整体移除的图层组。\n * @param map - A Leaflet map instance. Leaflet 地图实例。\n * @param items - Stations to plot (report + position + title). 待上图的站点列表。\n * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。\n *\n * 异步(v0.2 起为 Promise):leaflet 由首次调用时动态装载——本包可在任何模块图(含 Node/SSR\n * 预渲染流水线)中 import 而不触雷,代价是上图动作需 await。\n */\n\nexport async function addMetarLayer(\n map: Leaflet.Map,\n items: readonly MetarLayerItem[],\n options: AddMetarLayerOptions = {},\n): Promise<Leaflet.LayerGroup> {\n // 未知选项运行时抛错:拼写错误的选项被静默忽略 = 显示语言/行为悄悄不符预期(2026-09-15\n // 五角色评测实测:顶层 locale 此前被静默忽略,popup 整卡仍中文)。中文提示 = v0.1 message 语言契约。\n for (const key of Object.keys(options)) {\n if (!ADD_METAR_LAYER_OPTION_KEYS.has(key)) {\n throw new Error(\n `addMetarLayer 收到未知选项 \"${key}\"——卡片级选项(locale/raw/className…)需包在 card 里传,可用项见 AddMetarLayerOptions`,\n );\n }\n }\n // 语言解析单一出口:card.locale 显式传入 > 顶层 locale 速记 > zh;非法值清晰报错不裸崩\n const locale = options.card?.locale ?? options.locale ?? \"zh\";\n if (locale !== \"zh\" && locale !== \"en\") {\n // never 收窄后的宽化中转:模板表达式不接受 never 字面量类型\n const bad: string = locale;\n throw new Error(`addMetarLayer 的 locale 选项值 \"${bad}\" 不受支持(可用:\"zh\" | \"en\")`);\n }\n const L = await loadLeaflet();\n const group = L.layerGroup();\n for (const item of items) {\n const name = item.title ?? item.report.station;\n // alt 写入图标 img 的 alt 属性:marker 在读屏下 role=button,可访问名称 = 站名(WCAG 4.1.2)\n let marker: Leaflet.Marker;\n if (options.conditionColors === true) {\n // 圆点 divIcon 无 img——alt 失效,改以 role=img + aria-label 保住可访问名称(内容经 HTML 转义);\n // aria-label 追加档位词(a11y 1.4.1:档位不只靠颜色传达;语言随 card.locale,缺省中文)\n const tier = conditionOf(item.report);\n const label = `${name} · ${TIER_WORDS[locale][tier]}`;\n marker = L.marker(item.position, {\n icon: L.divIcon({\n className: \"mw-cond-icon\",\n html: `<span role=\"img\" aria-label=\"${escapeHtml(label)}\" class=\"mw-dot mw-dot-${tier}\" style=\"display:inline-block;width:12px;height:12px;border-radius:50%;background:${TIER_COLORS[tier]};border:2px solid #fff;box-shadow:0 0 2px rgba(0,0,0,.4)\"></span>`,\n iconSize: [14, 14],\n iconAnchor: [7, 7],\n }),\n });\n } else {\n marker = L.marker(item.position, { alt: name });\n }\n marker.bindTooltip(tooltipContent(item, options.conditionColors === true, locale));\n if (options.popup ?? true) {\n // 弹窗内容是 renderCard 的 DOM 元素(createElement/textContent 构建),本就走非 HTML 路径;\n // maxWidth 420 = 卡片设计宽(Leaflet 缺省 300 会压窄)\n // 元数据联表得到的站名随弹窗进卡片站名行(title = \"ICAO 站名\",剥掉 ICAO 前缀);调用方显式传入时以其为准\n const cardOpts: Parameters<typeof renderCard>[1] = { ...options.card };\n if (cardOpts.locale === undefined && options.locale !== undefined) {\n cardOpts.locale = options.locale;\n }\n const stationName =\n item.title !== undefined && item.title.startsWith(`${item.report.station} `)\n ? item.title.slice(item.report.station.length + 1)\n : undefined;\n if (cardOpts.stationTitle === undefined && stationName !== undefined) {\n cardOpts.stationTitle = stationName;\n }\n marker.bindPopup(renderCard(item.report, cardOpts), { maxWidth: 420 });\n marker.on(\"popupopen\", () => {\n // 触屏双浮层消除:弹窗打开即收起 tooltip\n marker.closeTooltip();\n // 焦点移入弹窗首个可聚焦元素(Leaflet 关闭按钮)——键盘与读屏可直达\n const focusTarget = marker\n .getPopup()\n ?.getElement()\n ?.querySelector<HTMLElement>(\"a.leaflet-popup-close-button, button, [href]\");\n focusTarget?.focus();\n });\n }\n marker.addTo(group);\n }\n if (!escapeBoundMaps.has(map)) {\n escapeBoundMaps.add(map);\n // 地图容器键盘路径:Escape 关闭已开弹窗(closePopup 对未开弹窗是空操作)\n map.getContainer().addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") map.closePopup();\n });\n }\n group.addTo(map);\n return group;\n}\n"],"mappings":";;;;;;;;;AAmBA,IAAI;AACJ,MAAM,oBAAwD,kBAAkB,OAAO;;AAoCvF,MAAM,8CAAmD,IAAI,IAAI;CAC/D;CACA;CACA;CACA;AACF,CAAC;;;;;AAMD,SAAS,YAAY,MAA2B;CAC9C,MAAM,KAAK,SAAS,cAAc,MAAM;CACxC,GAAG,cAAc;CACjB,OAAO;AACT;;AAGA,MAAM,cAAc,SAClB,KAAK,QAAQ,aAAa,OAAO;CAC/B,QAAQ,IAAR;EACE,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,MACH,OAAO;EACT,SACE,OAAO;CACX;AACF,CAAC;;AAGH,MAAM,cAA6C;CACjD,SAAS;CACT,MAAM;CACN,SAAS;CACT,MAAM;AACR;;AAKA,MAAM,aAAiE;CACrE,IAAI;EAAE,SAAS;EAAQ,MAAM;EAAO,SAAS;EAAQ,MAAM;CAAM;CACjE,IAAI;EACF,SAAS;EACT,MAAM;EACN,SAAS;EACT,MAAM;CACR;AACF;;AAGA,MAAM,mCAAwC,IAAI,IAAI;CACpD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAM,SAAS,OAAe,SAC5B,SAAS,OAAO,QAAQ,UAAW,SAAS,QAAQ,QAAQ,MAAM;;;;;;;;;;;;;AAcpE,SAAS,YAAY,QAAoC;CACvD,IAAI,OAAO,QAAQ,MAAM,OAAO;CAChC,MAAM,IAAI,SAAS,MAAM;CAEzB,MAAM,aAAa,OAAO,YAAY,SAAS;CAC/C,MAAM,WAAW,EAAE,QAAQ,YAAY,CAAC;CACxC,MAAM,mBACJ,CAAC,OAAO,SACR,SAAS,OACN,MAAM,EAAE,SAAS,UAAU,SAAS,EAAE,SAAS,yBAAyB,EAAE,WAAW,KACxF;CACF,MAAM,uBACJ,OAAO,YAAY,KAAA,KACnB,OAAO,QAAQ,SAAS,aACvB,OAAO,QAAQ,SAAS,WAAW,OAAO,QAAQ,MAAM,WAAW;CACtE,IAAI,cAAc,oBAAoB,sBAAsB,OAAO;CAGnE,MAAM,MAAM,EAAE;CACd,IAAI,QAAQ,KAAA,GACQ;OAAA,IAAI,SAAS,MAAM,IAAI,QAAQ,IAAI,QAAQ,YAC7C,MAAM,OAAO;CAAA;CAE/B,MAAM,WAAW,SACd,QAAQ,MAAqD,EAAE,SAAS,OAAO,CAAC,CAChF,QAAQ,MAAM,EAAE,WAAW,SAAS,EAAE,WAAW,KAAK,CAAC,CACvD,KAAK,MAAM,EAAE,SAAS,SAAS,OAAO,iBAAiB;CAC1D,KAAK,MAAM,KAAK,UACd,IAAI,EAAE,SAAS,uBACb,SAAS,KAAK,EAAE,SAAS,SAAS,OAAO,iBAAiB;CAE9D,MAAM,UAAU,SAAS,SAAS,IAAI,KAAK,IAAI,GAAG,QAAQ,IAAI,OAAO;CACrE,IAAI,UAAU,KAAM,OAAO;CAC3B,KAAK,MAAM,KAAK,EAAE,WAAW,CAAC,GAAG;EAC/B,MAAM,eAAe,EAAE,eAAe;EACtC,MAAM,YAAY,EAAE,UAAU,SAAS,IAAI,KAAK,EAAE,UAAU,SAAS,IAAI;EACzE,MAAM,WAAW,EAAE,eAAe;EAClC,MAAM,cACJ,EAAE,cAAc,QACf,EAAE,eAAe,QAAQ,EAAE,UAAU,MAAM,MAAM,iBAAiB,IAAI,CAAC,CAAC;EAC3E,IAAI,gBAAgB,aAAa,YAAY,aAAa,OAAO;CACnE;CACA,MAAM,OAAO,EAAE,MAAM;CACrB,IAAI,SAAS,KAAA,KAAa,MAAM,KAAK,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO;CAKrE,IAJmB,SAAS,MACzB,MACC,EAAE,SAAS,WAAW,EAAE,eAAe,KAAA,CAE9B,GAAG,OAAO;CACvB,IAAI,EAAE,aAAa,MAAM,OAAO,GAAG,WAAW,IAAI,GAAG,OAAO;CAG5D,IAAI,QAAQ,KAAA,GACQ;OAAA,IAAI,SAAS,MAAM,IAAI,QAAQ,IAAI,QAAQ,YAC7C,KAAM,OAAO;CAAA;CAE/B,IAAI,UAAU,KAAM,OAAO;CAC3B,KAAK,MAAM,KAAK,EAAE,WAAW,CAAC,GAE5B,IADe,EAAE,UAAU,MAAM,MAAM,iBAAiB,IAAI,CAAC,CACpD,GAAG,OAAO;CAErB,IAAI,SAAS,KAAA,KAAa,MAAM,KAAK,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO;CACrE,OAAO;AACT;;AAGA,SAAS,YAAY,QAAqB,GAAyB;CACjE,OAAO,EAAE,SAAS,KAAA,IACd,GAAG,EAAE,YAAY,OAAO,KAAK,EAAE,aAAa,KAAK,EAAE,cAAc,KAAK,EAAE,UAAU,KAAK,EAAE,MACzF,OAAO,IAAI,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,GAAG;AAC/C;;AAGA,SAAS,UAAU,QAAqB,OAA6B;CACnE,IAAI,MAAM,SAAS,KAAA,GAAW;EAC5B,IAAI,MAAM,SAAS,uBACjB,OAAO,KAAK,MAAM,SAAS,UAAU,OAAO,QAAQ,OAAO,KAAK,MAAM,MAAM,SAAS,QAAQ,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;EAEpH,MAAM,SACJ,MAAM,SAAS,UAAU,OACrB,QACA,OAAO,KAAK,MAAM,MAAM,SAAS,QAAQ,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;EACpE,OAAO,GAAG,MAAM,UAAU,QAAQ,SAAS,MAAM,cAAc;CACjE;CACA,OAAO,OAAO,IAAI,MAAM,MAAM,KAAK,OAAO,MAAM,KAAK,GAAG;AAC1D;;;;;;AAOA,SAAS,gBAAgB,QAAqB,QAA6B;CACzE,IAAI,OAAO,QAAQ,MAAM,OAAO,WAAW,OAAO,oBAAoB;CACtE,IAAI,OAAO,OAAO,OAAO;CACzB,MAAM,IAAI,SAAS,MAAM;CAEzB,MAAM,WAAW,EAAE,QAAQ,YAAY,CAAC;CACxC,MAAM,aAAa,OAAO,YAAY,SAAS;CAC/C,MAAM,mBACJ,CAAC,OAAO,SACR,SAAS,OACN,MAAM,EAAE,SAAS,UAAU,SAAS,EAAE,SAAS,yBAAyB,EAAE,WAAW,KACxF;CACF,MAAM,uBACJ,OAAO,YAAY,KAAA,KACnB,OAAO,QAAQ,SAAS,aACvB,OAAO,QAAQ,SAAS,WAAW,OAAO,QAAQ,MAAM,WAAW;CACtE,IAAI,cAAc,oBAAoB,sBACpC,OAAO,WAAW,OAAO,iBAAiB;CAC5C,MAAM,QAAkB,CAAC;CACzB,MAAM,MAAM,EAAE;CACd,IAAI,QAAQ,KAAA,GACV,MAAM,KACJ,IAAI,SAAS,MACT,IAAI,QACF,GAAG,IAAI,MAAM,MACb,WACF,IAAI,WAAW,UACb,IAAI,IAAI,MAAM,OACd,IAAI,WAAW,UACb,IAAI,IAAI,MAAM,OACd,GAAG,IAAI,MAAM,IACvB;CAEF,MAAM,UAAU,EAAE,WAAW,CAAC;CAC9B,MAAM,cACJ,QAAQ,MACL,MAAM,EAAE,eAAe,QAAQ,EAAE,UAAU,SAAS,IAAI,KAAK,EAAE,cAAc,GAChF,KAAK,QAAQ;CACf,IAAI,gBAAgB,KAAA,GAAW,MAAM,KAAK,YAAY,QAAQ,WAAW,CAAC;CAC1E,MAAM,SAAS,EAAE,QAAQ,YAAY,CAAC;CACtC,MAAM,aAAa,OAAO,MACvB,MACC,EAAE,SAAS,WAAW,EAAE,eAAe,KAAA,CAC3C;CACA,MAAM,eAAe,OAClB,QAAQ,MAAqD,EAAE,SAAS,OAAO,CAAC,CAChF,QAAQ,MAAM,EAAE,WAAW,SAAS,EAAE,WAAW,KAAK,CAAC,CACvD,QACE,QAAQ,MACP,WAAW,KAAA,MACV,EAAE,SAAS,SAAS,OAAO,sBACzB,OAAO,SAAS,SAAS,OAAO,qBAC/B,IACA,QACN,KAAA,CACF;CACF,MAAM,WAAW,OAAO,MAAM,MAAM,EAAE,SAAS,qBAAqB;CACpE,MAAM,aAAa,cAAc,gBAAgB;CACjD,IAAI,eAAe,KAAA,GAAW,MAAM,KAAK,UAAU,QAAQ,UAAU,CAAC;MACjE,IAAI,EAAE,QAAQ,UAAU,KAAA,GAAW,MAAM,KAAK,EAAE,OAAO,MAAM,IAAI;CACtE,OAAO,MAAM,KAAK,GAAG;AACvB;;AAGA,SAAS,eACP,MACA,aACA,QACa;CACb,MAAM,YAAY,SAAS,cAAc,MAAM;CAC/C,UAAU,OAAO,YAAY,KAAK,SAAS,KAAK,OAAO,OAAO,CAAC;CAC/D,IAAI,aACF,UAAU,OACR,SAAS,cAAc,IAAI,GAC3B,YAAY,gBAAgB,KAAK,QAAQ,MAAM,CAAC,CAClD;CAEF,OAAO;AACT;;AAGA,MAAM,kCAAkB,IAAI,QAAqB;;;;;;;;;;;AAajD,eAAsB,cACpB,KACA,OACA,UAAgC,CAAC,GACJ;CAG7B,KAAK,MAAM,OAAO,OAAO,KAAK,OAAO,GACnC,IAAI,CAAC,4BAA4B,IAAI,GAAG,GACtC,MAAM,IAAI,MACR,yBAAyB,IAAI,qEAC/B;CAIJ,MAAM,SAAS,QAAQ,MAAM,UAAU,QAAQ,UAAU;CACzD,IAAI,WAAW,QAAQ,WAAW,MAGhC,MAAM,IAAI,MAAM,+BAA+BA,OAAI,uBAAuB;CAE5E,MAAM,IAAI,MAAM,YAAY;CAC5B,MAAM,QAAQ,EAAE,WAAW;CAC3B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,KAAK,SAAS,KAAK,OAAO;EAEvC,IAAI;EACJ,IAAI,QAAQ,oBAAoB,MAAM;GAGpC,MAAM,OAAO,YAAY,KAAK,MAAM;GACpC,MAAM,QAAQ,GAAG,KAAK,KAAK,WAAW,OAAO,CAAC;GAC9C,SAAS,EAAE,OAAO,KAAK,UAAU,EAC/B,MAAM,EAAE,QAAQ;IACd,WAAW;IACX,MAAM,gCAAgC,WAAW,KAAK,EAAE,yBAAyB,KAAK,oFAAoF,YAAY,MAAM;IAC5L,UAAU,CAAC,IAAI,EAAE;IACjB,YAAY,CAAC,GAAG,CAAC;GACnB,CAAC,EACH,CAAC;EACH,OACE,SAAS,EAAE,OAAO,KAAK,UAAU,EAAE,KAAK,KAAK,CAAC;EAEhD,OAAO,YAAY,eAAe,MAAM,QAAQ,oBAAoB,MAAM,MAAM,CAAC;EACjF,IAAI,QAAQ,SAAS,MAAM;GAIzB,MAAM,WAA6C,EAAE,GAAG,QAAQ,KAAK;GACrE,IAAI,SAAS,WAAW,KAAA,KAAa,QAAQ,WAAW,KAAA,GACtD,SAAS,SAAS,QAAQ;GAE5B,MAAM,cACJ,KAAK,UAAU,KAAA,KAAa,KAAK,MAAM,WAAW,GAAG,KAAK,OAAO,QAAQ,EAAE,IACvE,KAAK,MAAM,MAAM,KAAK,OAAO,QAAQ,SAAS,CAAC,IAC/C,KAAA;GACN,IAAI,SAAS,iBAAiB,KAAA,KAAa,gBAAgB,KAAA,GACzD,SAAS,eAAe;GAE1B,OAAO,UAAU,WAAW,KAAK,QAAQ,QAAQ,GAAG,EAAE,UAAU,IAAI,CAAC;GACrE,OAAO,GAAG,mBAAmB;IAE3B,OAAO,aAAa;IAMpB,CAJoB,OACjB,SAAS,CAAC,EACT,WAAW,CAAC,EACZ,cAA2B,8CAA8C,EAAA,EAChE,MAAM;GACrB,CAAC;EACH;EACA,OAAO,MAAM,KAAK;CACpB;CACA,IAAI,CAAC,gBAAgB,IAAI,GAAG,GAAG;EAC7B,gBAAgB,IAAI,GAAG;EAEvB,IAAI,aAAa,CAAC,CAAC,iBAAiB,YAAY,UAAU;GACxD,IAAI,MAAM,QAAQ,UAAU,IAAI,WAAW;EAC7C,CAAC;CACH;CACA,MAAM,MAAM,GAAG;CACf,OAAO;AACT"}
1
+ {"version":3,"file":"index.js","names":["bad"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @metweave/leaflet — Leaflet 适配器:报文卡片上图的桥接层。\n * The Leaflet adapter: puts metweave report cards onto a Leaflet map.\n *\n * v0.1 唯一地图适配器;更多地图库适配(MapLibre 等)在路线图上。\n * 兼容契约 = Leaflet overlay 能力:底图瓦片是宿主侧一行 L.tileLayer 配置,本包不做任何绑定,\n * 也不推荐或代理任何底图服务——底图由使用者按自己的授权自行选择。\n */\nimport type * as Leaflet from \"leaflet\";\nimport type {\n CloudCondition,\n CloudElement,\n MetarReport,\n Observed,\n RunwayStateGroup,\n VisibilityGroup,\n WeatherGroup,\n WindGroup,\n} from \"@metweave/core\";\nimport { toValues, unwrap } from \"@metweave/core\";\nimport { renderCard, renderTafCard, summarizeTafConditions } from \"@metweave/render\";\nimport type { RenderTafCardOptions, TafCalendarAnchor } from \"@metweave/render\";\nimport { expandTaf } from \"@metweave/parser\";\nimport type {\n TafExpandAt,\n TafMonthAnchor,\n TafReport,\n TafResolvedConditions,\n} from \"@metweave/parser\";\n\n// 公开选项(AddTafLayerOptions.at / TafTimeControlOptions.initialAt)以 TafExpandAt 为形参、\n// TafLayerItem.report 以 TafReport 为形参——类型随包再导出,宿主(如 examples)不必穿透到 @metweave/parser 取型\nexport type { TafExpandAt, TafReport, TafCalendarAnchor };\n\n/**\n * leaflet 惰性装载:import 本包时不再静态加载 leaflet——peer 库是浏览器专属实现,\n * 顶层静态 import 会让 Node/SSR 的模块图在求值期即崩(window is not defined,\n * 2026-09-16 五方实测评测实锤:ESM/CJS 双格式 import 均炸)。改为首次 addMetarLayer\n * 调用时动态 import 并缓存;类型面仍由顶层 import type 提供,宿主类型检查零影响。\n */\nlet leafletModule: Promise<typeof import(\"leaflet\")> | undefined;\nconst loadLeaflet = (): Promise<typeof import(\"leaflet\")> => (leafletModule ??= import(\"leaflet\"));\n\n/**\n * One station on the map: parsed report IR + WGS-84 position + optional tooltip title.\n * 地图上的一个站点:已解析报文 IR + WGS-84 坐标 + 可选悬浮标题。\n */\nexport interface MetarLayerItem {\n /** 已解析的报文 IR */\n report: MetarReport;\n /** 站点坐标(WGS-84 [lat, lon];用 GCJ-02 底图时的偏移由宿主决定是否校正) */\n position: [number, number];\n /** 站点提示(tooltip 文案,缺省用 IR 站名)。按纯文本处理——不解析 HTML,宿主自传任意串无注入面 */\n title?: string;\n}\n\n/**\n * Options for addMetarLayer: popup behavior, pass-through renderCard options, and the condition-color dot mode.\n * addMetarLayer 的选项:弹窗行为、透传 renderCard 的选项、条件色圆点模式。\n */\nexport interface AddMetarLayerOptions {\n /** 显示语言速记:等价 `card.locale`,同时决定 tooltip 摘要/档位词与弹窗卡片语言;\n * card.locale 显式传入时以其为准。未知选项运行时抛错(拼写错误不静默) */\n locale?: \"zh\" | \"en\";\n /** 点击站点时以弹窗展示报文卡片(缺省开启) */\n popup?: boolean;\n /** 传给 renderCard 的选项 */\n card?: Parameters<typeof renderCard>[1];\n /** marker 按四档气象条件着色(unknown/poor/caution/good 圆点替代默认图钉;缺省 false 保持现状)。\n * 判据为本库自拟的扫视启发式(初稿待审,见 conditionOf 注释)——不对应任何官方飞行天气分类,\n * 不得用作运行判据;\n * 开启时 tooltip 同时追加一行要素摘要(能见度/天气/最差云;NIL 与关键组全缺测站显示「缺报/数据缺测」),\n * 圆点 aria-label 追加档位词(zh「天气好/差…」/ en「Weather …」,语言随 card.locale) */\n conditionColors?: boolean;\n /** bindPopup 选项透传(autoPanPadding 族等——宿主为固定悬浮层留避让边时用;\n * 本库缺省只设 maxWidth=卡片设计宽,宿主显式键覆盖之) */\n popupOptions?: Leaflet.PopupOptions;\n}\n\n/** addMetarLayer 的合法选项键(运行时校验用——拼错的选项键静默忽略违反本库不静默纪律) */\nconst ADD_METAR_LAYER_OPTION_KEYS: ReadonlySet<string> = new Set([\n \"locale\",\n \"popup\",\n \"card\",\n \"conditionColors\",\n \"popupOptions\",\n]);\n\n/**\n * tooltip 纯文本承载:Leaflet 对 string 内容走 innerHTML——宿主自传的 title 是任意串,\n * 经由 textContent 装载(非 HTML 路径)杜绝标记注入;文案渲染不受影响。\n */\nfunction textCarrier(text: string): HTMLElement {\n const el = document.createElement(\"span\");\n el.textContent = text;\n return el;\n}\n\n/** HTML 属性上下文转义(divIcon 的 html 字符串构造用——title/站名不可信面) */\nconst escapeHtml = (text: string): string =>\n text.replace(/[&<>\"']/g, (ch) => {\n switch (ch) {\n case \"&\":\n return \"&amp;\";\n case \"<\":\n return \"&lt;\";\n case \">\":\n return \"&gt;\";\n case '\"':\n return \"&quot;\";\n default:\n return \"&#39;\";\n }\n });\n\n/** 四档条件档位名(与 TIER_COLORS 的键一致;宿主面板/图例数据直读用,2026-09-24 评测批2#3) */\nexport type ConditionTier = \"unknown\" | \"poor\" | \"caution\" | \"good\";\n\n/**\n * 四档条件色单一来源(2026-09-24 评测批2#3:图例/面板/圆点色值不一致——两套色系并存)。\n * 宿主图例与列表色点应引用本表而非自抄色值(编译期同源,显示层永不漂移);\n * 与卡片 mw-danger/mw-caution 同族色相:灰=不明、红=差、琥珀=注意、绿=好\n */\nexport const TIER_COLORS: Record<ConditionTier, string> = {\n unknown: \"#8a94a0\",\n poor: \"#d05656\",\n caution: \"#e0a13c\",\n good: \"#3aa657\",\n};\n\n/** 档位可读名(aria-label 追加词——a11y 1.4.1:档位信息不只靠颜色传达) */\nconst TIER_WORDS: Record<\"zh\" | \"en\", Record<ConditionTier, string>> = {\n zh: { unknown: \"天气不明\", poor: \"天气差\", caution: \"天气注意\", good: \"天气好\" },\n en: {\n unknown: \"Weather unknown\",\n poor: \"Weather poor\",\n caution: \"Weather caution\",\n good: \"Weather good\",\n },\n};\n\n/** 降水类现象(caution 判据用,与 WMO 4678 降水族对应——RA/SN/SG/PL/GS/IC/DZ/UP) */\nconst PRECIP_PHENOMENA: ReadonlySet<string> = new Set([\n \"RA\",\n \"SN\",\n \"SG\",\n \"PL\",\n \"GS\",\n \"IC\",\n \"DZ\",\n \"UP\",\n]);\n\n/** 风速折米/秒(阵风判据统一单位:kt ×0.514444、kmh ÷3.6、mps ×1) */\nconst toMps = (value: number, unit: \"kt\" | \"mps\" | \"kmh\"): number =>\n unit === \"kt\" ? value * 0.514444 : unit === \"kmh\" ? value / 3.6 : value;\n\n/**\n * 四档气象条件分级(判据本库自拟、初稿待审——显示层扫视启发式):\n * 阈值由本库拟定,**不对应也不代表任何官方飞行天气分类;本库不提供飞行规则判定**(本期无此功能)。\n * 仅供「一眼扫视哪些站值得注意」,不得作为任何运行判据:\n * - unknown(灰)= NIL(台站无观测)或关键组全缺测(能见度与云均缺测且天气缺测/无——按可得要素无从判读)\n * - poor(红)= 能见度 < 1500 m,或 BKN/OVC 云层(含垂直能见度)云底 < 1000 ft,或天气含 TS 族(任何雷暴,含 VC 邻近)\n * 或现象含 GR/VA,或冻降水(FZ 描述符族,冻雨/冻毛毛雨),或 + 强度显著降水,或阵风 ≥ 25 m/s,或云组含 CB/TCU,或跑道关闭\n * - caution(琥珀)= 能见度 1500–5000 m(能见度分档取国内通行 1500/5000 m 口径),或 BKN/OVC 云底 1000–3000 ft,或任何降水族(RA/SN 等),\n * 或 FZ 描述符以外的结冰现象,或阵风 15–25 m/s\n * - good(绿)= 其余(含 CAVOK)\n * 缺测要素不参与限制(按可得要素判,见 conditionOf 内 unknown 判据的例外);阈值细则随口径审定后修订。\n */\n/** 判据输入面(结构子集):METAR 报与 TAF 展开结果皆可喂(渲染层①,2026-09-23)——\n * MetarReport 结构性满足本接口;TAF 侧由展开结果投影构造(runwayStates 恒缺省) */\ninterface ConditionInput {\n readonly nil?: boolean;\n readonly cavok: boolean;\n readonly wind?: Observed<WindGroup>;\n readonly visibility?: Observed<VisibilityGroup>;\n readonly weather?: Observed<readonly WeatherGroup[]>;\n readonly clouds?: CloudCondition;\n readonly runwayStates?: readonly RunwayStateGroup[];\n}\n\nfunction conditionOf(report: ConditionInput): ConditionTier {\n if (report.nil === true) return \"unknown\";\n const v = {\n wind: unwrap(report.wind),\n visibility: unwrap(report.visibility),\n weather: unwrap(report.weather),\n clouds: report.clouds,\n runwayStates: report.runwayStates ?? [],\n };\n // 关键组全缺测:能见度与云均缺测(云组每个体均为全缺测形态)且天气缺测/无——判读无从下手,灰而非绿\n const visMissing = report.visibility?.kind === \"missing\";\n const elements = v.clouds?.elements ?? [];\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone) return \"unknown\";\n\n // —— poor 判据(任一命中即红)\n const vis = v.visibility;\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 1500) return \"poor\";\n }\n const ceilings = elements\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .map((e) => e.heightFt.value ?? Number.POSITIVE_INFINITY);\n for (const e of elements) {\n if (e.kind === \"vertical-visibility\")\n ceilings.push(e.heightFt.value ?? Number.POSITIVE_INFINITY);\n }\n const ceiling = ceilings.length > 0 ? Math.min(...ceilings) : Number.POSITIVE_INFINITY;\n if (ceiling < 1000) return \"poor\";\n for (const g of v.weather ?? []) {\n const thunderstorm = g.descriptor === \"TS\"; // TS 族:任何雷暴(含 VCTS 邻近雷暴)\n const hailOrAsh = g.phenomena.includes(\"GR\") || g.phenomena.includes(\"VA\");\n const freezing = g.descriptor === \"FZ\"; // 冻降水族(FZRA/FZDZ 等)——危害与雷暴同级,2026-09-22 运行视角评审升红\n const heavyPrecip =\n g.intensity === \"+\" &&\n (g.descriptor === \"SH\" || g.phenomena.some((p) => PRECIP_PHENOMENA.has(p)));\n if (thunderstorm || hailOrAsh || freezing || heavyPrecip) return \"poor\";\n }\n const gust = v.wind?.gust;\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 25) return \"poor\";\n const convective = elements.some(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n if (convective) return \"poor\";\n if (v.runwayStates.some((st) => st.closed === true)) return \"poor\";\n\n // —— caution 判据(任一命中即琥珀)\n if (vis !== undefined) {\n const visMeters = vis.unit === \"m\" ? vis.value : vis.value * 1609.344;\n if (visMeters < 5000) return \"caution\";\n }\n if (ceiling < 3000) return \"caution\";\n for (const g of v.weather ?? []) {\n const precip = g.phenomena.some((p) => PRECIP_PHENOMENA.has(p));\n if (precip) return \"caution\";\n }\n if (gust !== undefined && toMps(gust.value, gust.unit) >= 15) return \"caution\";\n return \"good\";\n}\n\n/** 天气组显示码:span 在位取原码,缺席由 IR 重建(摘要行的要素原样口径) */\nfunction weatherCode(report: MetarReport, g: WeatherGroup): string {\n return g.span === undefined\n ? `${g.proximity ? \"VC\" : \"\"}${g.intensity ?? \"\"}${g.descriptor ?? \"\"}${g.phenomena.join(\"\")}`\n : report.raw.slice(g.span.start, g.span.end);\n}\n\n/** 云层显示码:span 在位取原码,缺席由 IR 重建(缺测位还原为 ///) */\nfunction cloudCode(report: MetarReport, layer: CloudElement): string {\n if (layer.span === undefined) {\n if (layer.kind === \"vertical-visibility\") {\n return `VV${layer.heightFt.value === null ? \"///\" : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\")}`;\n }\n const height =\n layer.heightFt.value === null\n ? \"///\"\n : String(Math.round(layer.heightFt.value / 100)).padStart(3, \"0\");\n return `${layer.amount ?? \"///\"}${height}${layer.convective ?? \"\"}`;\n }\n return report.raw.slice(layer.span.start, layer.span.end);\n}\n\n/**\n * tooltip 第二行要素摘要(扫视初筛):`2500m +TSRA BKN030CB` 式——\n * 能见度 / 最显著天气(优先 TS/GR/+ 强度族)/ 最差云(对流云优先,否则最低 BKN/OVC,VV 兜底)。\n * NIL 站显示「缺报(NIL)」、关键组全缺测站显示「数据缺测」(与 conditionOf 的 unknown 判据同款口径)。\n */\nfunction summarizeReport(report: MetarReport, locale: \"zh\" | \"en\"): string {\n if (report.nil === true) return locale === \"en\" ? \"No report (NIL)\" : \"缺报(NIL)\";\n if (report.cavok) return \"CAVOK\";\n const v = toValues(report);\n // 关键组全缺测(能见度与云均缺测且天气缺测/无):要素摘要无从拼起,显示缺测占位而非空行\n const elements = v.clouds?.elements ?? [];\n const visMissing = report.visibility?.kind === \"missing\";\n const cloudsAllMissing =\n !report.cavok &&\n elements.every(\n (e) => e.heightFt.value === null && (e.kind === \"vertical-visibility\" || e.amount === null),\n );\n const weatherMissingOrNone =\n report.weather === undefined ||\n report.weather.kind === \"missing\" ||\n (report.weather.kind === \"value\" && report.weather.value.length === 0);\n if (visMissing && cloudsAllMissing && weatherMissingOrNone)\n return locale === \"en\" ? \"Data missing\" : \"数据缺测\";\n const parts: string[] = [];\n const vis = v.visibility;\n if (vis !== undefined) {\n parts.push(\n vis.unit === \"m\"\n ? vis.exact\n ? `${vis.value} m`\n : \"≥10 km\"\n : vis.beyond === \"below\"\n ? `<${vis.value} SM`\n : vis.beyond === \"above\"\n ? `>${vis.value} SM`\n : `${vis.value} SM`,\n );\n }\n const weather = v.weather ?? [];\n const significant =\n weather.find(\n (g) => g.descriptor === \"TS\" || g.phenomena.includes(\"GR\") || g.intensity === \"+\",\n ) ?? weather[0];\n if (significant !== undefined) parts.push(weatherCode(report, significant));\n const layers = v.clouds?.elements ?? [];\n const convective = layers.find(\n (e): e is Extract<CloudElement, { kind: \"layer\" }> =>\n e.kind === \"layer\" && e.convective !== undefined,\n );\n const ceilingLayer = layers\n .filter((e): e is Extract<CloudElement, { kind: \"layer\" }> => e.kind === \"layer\")\n .filter((e) => e.amount === \"BKN\" || e.amount === \"OVC\")\n .reduce<Extract<CloudElement, { kind: \"layer\" }> | undefined>(\n (lowest, e) =>\n lowest === undefined ||\n (e.heightFt.value ?? Number.POSITIVE_INFINITY) <\n (lowest.heightFt.value ?? Number.POSITIVE_INFINITY)\n ? e\n : lowest,\n undefined,\n );\n const vertical = layers.find((e) => e.kind === \"vertical-visibility\");\n const worstCloud = convective ?? ceilingLayer ?? vertical;\n if (worstCloud !== undefined) parts.push(cloudCode(report, worstCloud));\n else if (v.clouds?.clear !== undefined) parts.push(v.clouds.clear.code);\n return parts.join(\" \");\n}\n\n/** tooltip 内容节点:标题行 + 要素摘要行(两行皆纯文本装载;locale 决定摘要占位词语言) */\nfunction tooltipContent(\n item: MetarLayerItem,\n withSummary: boolean,\n locale: \"zh\" | \"en\",\n): HTMLElement {\n const container = document.createElement(\"span\");\n container.append(textCarrier(item.title ?? item.report.station));\n if (withSummary) {\n container.append(\n document.createElement(\"br\"),\n textCarrier(summarizeReport(item.report, locale)),\n );\n }\n return container;\n}\n\n/** Escape 关闭已开弹窗的监听只挂一次/地图(多图层叠加不重复绑定) */\nconst escapeBoundMaps = new WeakSet<Leaflet.Map>();\n\n/**\n * Put a set of report stations onto a Leaflet map: markers + tooltips + card popups. Returns a removable layer group.\n * 把一组报文站点挂上地图:标记 + tooltip + 卡片弹窗。返回可整体移除的图层组。\n * @param map - A Leaflet map instance. Leaflet 地图实例。\n * @param items - Stations to plot (report + position + title). 待上图的站点列表。\n * @param options - See AddMetarLayerOptions. 见 AddMetarLayerOptions。\n *\n * 异步(v0.2 起为 Promise):leaflet 由首次调用时动态装载——本包可在任何模块图(含 Node/SSR\n * 预渲染流水线)中 import 而不触雷,代价是上图动作需 await。\n */\n\nexport async function addMetarLayer(\n map: Leaflet.Map,\n items: readonly MetarLayerItem[],\n options: AddMetarLayerOptions = {},\n): Promise<Leaflet.LayerGroup> {\n // 未知选项运行时抛错:拼写错误的选项被静默忽略 = 显示语言/行为悄悄不符预期(2026-09-15\n // 五角色评测实测:顶层 locale 此前被静默忽略,popup 整卡仍中文)。中文提示 = v0.1 message 语言契约。\n for (const key of Object.keys(options)) {\n if (!ADD_METAR_LAYER_OPTION_KEYS.has(key)) {\n throw new Error(\n `addMetarLayer 收到未知选项 \"${key}\"——卡片级选项(locale/raw/className…)需包在 card 里传,可用项见 AddMetarLayerOptions`,\n );\n }\n }\n // 语言解析单一出口:card.locale 显式传入 > 顶层 locale 速记 > zh;非法值清晰报错不裸崩\n const locale = options.card?.locale ?? options.locale ?? \"zh\";\n if (locale !== \"zh\" && locale !== \"en\") {\n // never 收窄后的宽化中转:模板表达式不接受 never 字面量类型\n const bad: string = locale;\n throw new Error(`addMetarLayer 的 locale 选项值 \"${bad}\" 不受支持(可用:\"zh\" | \"en\")`);\n }\n const L = await loadLeaflet();\n const group = L.layerGroup();\n for (const item of items) {\n const name = item.title ?? item.report.station;\n // alt 写入图标 img 的 alt 属性:marker 在读屏下 role=button,可访问名称 = 站名(WCAG 4.1.2)\n let marker: Leaflet.Marker;\n if (options.conditionColors === true) {\n // 圆点 divIcon 无 img——alt 失效,改以 role=img + aria-label 保住可访问名称(内容经 HTML 转义);\n // aria-label 追加档位词(a11y 1.4.1:档位不只靠颜色传达;语言随 card.locale,缺省中文)\n const tier = conditionOf(item.report);\n const label = `${name} · ${TIER_WORDS[locale][tier]}`;\n marker = L.marker(item.position, {\n icon: L.divIcon({\n className: \"mw-cond-icon\",\n html: `<span role=\"img\" aria-label=\"${escapeHtml(label)}\" class=\"mw-dot mw-dot-${tier}\" style=\"display:inline-block;width:12px;height:12px;border-radius:50%;background:${TIER_COLORS[tier]};border:2px solid #fff;box-shadow:0 0 2px rgba(0,0,0,.4)\"></span>`,\n iconSize: [14, 14],\n iconAnchor: [7, 7],\n }),\n });\n } else {\n marker = L.marker(item.position, { alt: name });\n }\n marker.bindTooltip(tooltipContent(item, options.conditionColors === true, locale));\n if (options.popup ?? true) {\n // 弹窗内容是 renderCard 的 DOM 元素(createElement/textContent 构建),本就走非 HTML 路径;\n // maxWidth 420 = 卡片设计宽(Leaflet 缺省 300 会压窄)\n // 元数据联表得到的站名随弹窗进卡片站名行(title = \"ICAO 站名\",剥掉 ICAO 前缀);调用方显式传入时以其为准\n const cardOpts: Parameters<typeof renderCard>[1] = { ...options.card };\n if (cardOpts.locale === undefined && options.locale !== undefined) {\n cardOpts.locale = options.locale;\n }\n const stationName =\n item.title !== undefined && item.title.startsWith(`${item.report.station} `)\n ? item.title.slice(item.report.station.length + 1)\n : undefined;\n if (cardOpts.stationTitle === undefined && stationName !== undefined) {\n cardOpts.stationTitle = stationName;\n }\n marker.bindPopup(renderCard(item.report, cardOpts), {\n maxWidth: 420,\n ...options.popupOptions,\n });\n marker.on(\"popupopen\", () => {\n // 触屏双浮层消除:弹窗打开即收起 tooltip\n marker.closeTooltip();\n // 焦点移入弹窗首个可聚焦元素(Leaflet 关闭按钮)——键盘与读屏可直达\n const focusTarget = marker\n .getPopup()\n ?.getElement()\n ?.querySelector<HTMLElement>(\"a.leaflet-popup-close-button, button, [href]\");\n focusTarget?.focus();\n });\n }\n marker.addTo(group);\n }\n if (!escapeBoundMaps.has(map)) {\n escapeBoundMaps.add(map);\n // 地图容器键盘路径:Escape 关闭已开弹窗(closePopup 对未开弹窗是空操作)\n map.getContainer().addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") map.closePopup();\n });\n }\n group.addTo(map);\n return group;\n}\n\n// ---------------------------------------------------------------- TAF 图层(v0.2 渲染层①:预报当观测渲)\n\n/** TAF 展开结果 → 判据输入面投影(runwayStates 恒缺省——TAF 无跑道状态语汇) */\nfunction asConditionInput(c: TafResolvedConditions, nilLike: boolean): ConditionInput {\n return {\n nil: nilLike,\n cavok: c.cavok,\n ...(c.wind !== undefined ? { wind: { kind: \"value\" as const, value: c.wind } } : {}),\n ...(c.visibility !== undefined\n ? { visibility: { kind: \"value\" as const, value: c.visibility } }\n : {}),\n ...(c.weather.length > 0 ? { weather: { kind: \"value\" as const, value: c.weather } } : {}),\n ...(c.clouds !== undefined ? { clouds: c.clouds } : {}),\n };\n}\n\n/**\n * TAF 展开摘要(一行,人话优先——2026-09-24 评测批2#4:旧电码串「≥10 km · -TSRA · SCT040」\n * 对非专业是密码;现复用 @metweave/render 的 gloss 词表单一来源,tooltip/置顶提示同款):\n * 「能见度 ≥10 km · 雷暴伴雨(飞行威胁大) · 疏云,云底约 700 米」;电码在卡内 RAW 区可查。\n */\nfunction summarizeTaf(c: TafResolvedConditions, locale: \"zh\" | \"en\"): string {\n if (c.cavok) return \"CAVOK\";\n const text = summarizeTafConditions(c, locale);\n if (text !== \"\") return text;\n return locale === \"en\" ? \"No elements\" : \"无要素组\";\n}\n\nexport interface TafLayerItem {\n /** 已解析的 TAF 报文 IR */\n report: TafReport;\n /** 站点坐标(WGS-84 [lat, lon]) */\n position: [number, number];\n /** 站点提示(tooltip 文案,缺省用 IR 站名) */\n title?: string;\n /** 月锚(真实年月,month 1–12):该报文日号归属的日历月——在位且层带 calendarAnchor 时,\n * 层连续序 at 在展开/渲染前归一到本报锚月(跨月报池各自正确,2026-09-24 评测 P1 月界批);\n * 缺席时走层全局 anchorDays 的 %31 折回(显示位残余近似,见 anchorize 注释) */\n monthAnchor?: TafCalendarAnchor;\n}\n\n/**\n * Options for addTafLayer: expansion instant, month anchor, locale, popup.\n * addTafLayer 的选项:展开时刻、月锚、语言、弹窗。\n */\nexport interface AddTafLayerOptions {\n /** 显示语言(缺省 zh) */\n locale?: \"zh\" | \"en\";\n /** 展开时刻(UTC);缺省 = 有效期起点 */\n at?: TafExpandAt;\n /** 月锚天数(B3 跨月回绕,有效期起日所在月);缺省 31 */\n anchorDays?: number;\n /** 日历月锚(2026-09-24 评测 P1 月界批):at 视为「自该月 1 日起的连续日序」(day 可超月长),\n * 与各 item.monthAnchor 联用把 at 归一到每报锚月——跨月报池(如 9 月末混入 10 月 1 日生效报)\n * 展开与显示各报正确;在位时忽略 anchorDays(月天数按真实月历逐报计算) */\n calendarAnchor?: TafCalendarAnchor;\n /** 点击站点时以弹窗展示预报摘要(缺省开启;层② 的完整 TAF 卡片在后续版本) */\n popup?: boolean;\n /** renderTafCard 透传(raw/className/stationTitle/utcOffsetMinutes——宿主定制 TAF 卡;\n * at/monthAnchor 由图层按当前时刻与各报锚注入,不接受层级传入) */\n card?: Omit<RenderTafCardOptions, \"locale\" | \"at\" | \"monthAnchor\">;\n /** bindPopup 选项透传(autoPanPadding 族等——宿主为固定悬浮层留避让边时用;\n * 本库缺省只设 maxWidth=卡片设计宽 480,宿主显式键覆盖之) */\n popupOptions?: Leaflet.PopupOptions;\n}\n\nconst ADD_TAF_LAYER_OPTION_KEYS: ReadonlySet<string> = new Set([\n \"locale\",\n \"at\",\n \"anchorDays\",\n \"calendarAnchor\",\n \"popup\",\n \"card\",\n \"popupOptions\",\n]);\n\n/**\n * Put parsed TAF stations onto a Leaflet map as forecast markers at one instant — the four-tier\n * dot, tooltip and popup are driven by `expandTaf` (renderer layer ①: forecast-as-observation).\n * 把一组 TAF 站点按同一时刻的预报值挂上地图:四档圆点/tooltip/弹窗全部由 expandTaf 展开\n * 结果驱动(渲染层①「预报当观测渲」——判据/圆点/摘要与 METAR 侧同一条管线)。\n * 档位判据为本库自拟扫视启发式(同 conditionOf 注释)——**预报值套判据同样不得用作运行判据**,\n * tooltip/弹窗均显式标注「预报」,不与实况混淆。\n */\nexport async function addTafLayer(\n map: Leaflet.Map,\n items: readonly TafLayerItem[],\n options: AddTafLayerOptions = {},\n): Promise<Leaflet.LayerGroup> {\n for (const key of Object.keys(options)) {\n if (!ADD_TAF_LAYER_OPTION_KEYS.has(key)) {\n throw new Error(`addTafLayer 收到未知选项 \"${key}\"(可用项见 AddTafLayerOptions)`);\n }\n }\n const locale = options.locale ?? \"zh\";\n if (locale !== \"zh\" && locale !== \"en\") {\n const bad: string = locale;\n throw new Error(`addTafLayer 的 locale 选项值 \"${bad}\" 不受支持(可用:\"zh\" | \"en\")`);\n }\n const L = await loadLeaflet();\n const group = L.layerGroup();\n await populateTafLayer(map, group, items, options, L);\n group.addTo(map);\n return group;\n}\n\n/** 常显站码标签样式(评测签派 P0-1:无站码=「有红的看不出是谁红的」);缩放门控类由 zoomend 维护 */\nconst TAF_LAYER_STYLE_ID = \"mw-taf-layer-style\";\nconst injectTafLayerStyle = (): void => {\n if (document.getElementById(TAF_LAYER_STYLE_ID) !== null) return;\n const style = document.createElement(\"style\");\n style.id = TAF_LAYER_STYLE_ID;\n style.append(\n document.createTextNode(\n \".mw-code-label { position:absolute; left:15px; top:-3px; white-space:nowrap; font:600 10px/1.4 system-ui,sans-serif; color:#2c3e50; text-shadow:0 0 3px #fff,0 0 3px #fff,0 0 3px #fff; pointer-events:none; }\" +\n \".mw-hide-codes .mw-code-label { display:none; }\",\n ),\n );\n document.head.append(style);\n};\nconst codeZoomBound = new WeakSet<Leaflet.Map>();\n/** zoom ≥ 5 常显 ICAO 码,低于则收(38 站全显互相压盖;评测签派 P0-1 的缩放分级折中) */\nconst bindCodeZoom = (map: Leaflet.Map): void => {\n if (codeZoomBound.has(map)) return;\n codeZoomBound.add(map);\n const container = map.getContainer();\n const toggle = (): void => {\n container.classList.toggle(\"mw-hide-codes\", map.getZoom() < 5);\n };\n map.on(\"zoomend\", toggle);\n toggle();\n};\n\n/** 各层当前展开时刻(setTafLayerTime 换时刻不再清层重建——marker 原地 setIcon/setTooltipContent,评测工程 P2-1) */\nconst tafLayerAt = new WeakMap<Leaflet.LayerGroup, TafExpandAt>();\n/** 各层的日历月锚(2026-09-24 评测 P1 月界批):at 连续序「自该月 1 日起」的基准月 */\nconst tafLayerCalendar = new WeakMap<Leaflet.LayerGroup, TafCalendarAnchor>();\n/** 各层的可变 card 选项覆盖(时区单制:setTafLayerTime 带 card 即合并——切时区不清层重建、\n * 滑杆换时刻不带 card 不回退;弹窗刷新读此处而非建层闭包,已开弹窗即时随时区换内容) */\nconst tafLayerCard = new WeakMap<\n Leaflet.LayerGroup,\n Omit<RenderTafCardOptions, \"locale\" | \"at\" | \"monthAnchor\">\n>();\n/** marker → 其 TafLayerItem(换时刻原地更新时的数据面) */\nconst tafMarkerItems = new WeakMap<Leaflet.Marker, TafLayerItem>();\n/** marker → 弹窗内容刷新函数(复测 N1/N2:刷新不走合成 popupopen——真实打开才移焦点,刷新按当前时刻重算置顶提示) */\nconst tafPopupRefresh = new WeakMap<Leaflet.Marker, (popup: Leaflet.Popup) => void>();\n\n/** 查看时刻是否在该报文有效期外(含窗前/窗后——出窗=灰点「预报未生效/已过期」) */\n/** 日时 → 分钟序(出窗比较用;TAF 无月,同报文语境内日号自洽) */\nconst absDayHour = (d: number, h: number): number => (d - 1) * 1440 + h * 60;\n\n/** 真实月历:某年月的天数(2026-09-24 评测 P1 月界批) */\nconst daysInMonthOf = (year: number, month: number): number =>\n new Date(Date.UTC(year, month, 0)).getUTCDate();\n\n/** 年月 → 序数(锚距比较用) */\nconst calendarIndex = (c: TafCalendarAnchor): number => c.year * 12 + (c.month - 1);\n\n/**\n * 单站时刻归一(2026-09-24 评测 P1 月界批):\n * - 显式层时刻 contAt(calendarAnchor 基的连续日序,可超月长)→ 经 anchorize 换算到本报锚月;\n * - contAt 缺省(层尚无统一时刻)→ 各站自身有效期起点(已在本报锚内,原样直用,月天数按报锚真月历)。\n * 返回喂 expandTaf/tafMarkerState 的 at+daysIn,cal 供 renderTafCard 走真月历显示。\n */\nfunction stationNorm(\n item: TafLayerItem,\n contAt: TafExpandAt | undefined,\n layerCal: TafCalendarAnchor | undefined,\n fallbackDaysIn: number,\n): { at: TafExpandAt; daysIn: number; cal?: TafCalendarAnchor } {\n const v = item.report.validity;\n const ownStart: TafExpandAt = { day: v?.startDay ?? 0, hour: v?.startHour ?? 0, minute: 0 };\n const itemCal = item.monthAnchor;\n if (contAt === undefined) {\n return {\n at: ownStart,\n daysIn: itemCal !== undefined ? daysInMonthOf(itemCal.year, itemCal.month) : fallbackDaysIn,\n cal: itemCal,\n };\n }\n return anchorize(item, contAt, layerCal, fallbackDaysIn);\n}\n\n/**\n * 层连续序 at → 该报锚月内的归一(2026-09-24 评测 P1 月界批,跨月报池收口):\n * item.monthAnchor 与层 calendarAnchor 都在位时,把 at 的连续日序(自层锚月 1 日起、可超月长)\n * 换算到本报锚月内的日号,并按真实月历给该月天数(供报文 validity 回绕)——月末跨月的\n * 混合报池(9 月末的在效报 + 10 月 1 日生效报)展开与显示各报正确。\n * 缺任一锚(或锚病态相差超 24 个月)时原样返回 + 层全局 anchorDays 的 %31 折回——残余近似\n * 仅显示与回绕位:报文本身无月份,无锚即无从换算;档位判读的要素合成不依赖日号显示 */\nfunction anchorize(\n item: TafLayerItem,\n at: TafExpandAt,\n layerCal: TafCalendarAnchor | undefined,\n fallbackDaysIn: number,\n): { at: TafExpandAt; daysIn: number; cal?: TafCalendarAnchor } {\n const itemCal = item.monthAnchor;\n if (itemCal === undefined || layerCal === undefined) {\n return { at, daysIn: fallbackDaysIn };\n }\n const diff = calendarIndex(itemCal) - calendarIndex(layerCal);\n if (diff < -24 || diff > 24) {\n return { at, daysIn: fallbackDaysIn }; // 病态锚距(脏数据防御):折回近似兜底\n }\n // 报锚月 1 日相对层锚月 1 日的天数(逐月累加,双向)\n let firstCont = 1;\n let y = layerCal.year;\n let m = layerCal.month;\n for (let i = 0; i < Math.abs(diff); i += 1) {\n if (diff > 0) {\n firstCont += daysInMonthOf(y, m);\n m += 1;\n if (m > 12) {\n m = 1;\n y += 1;\n }\n } else {\n m -= 1;\n if (m < 1) {\n m = 12;\n y -= 1;\n }\n firstCont -= daysInMonthOf(y, m);\n }\n }\n return {\n at: { ...at, day: at.day - (firstCont - 1) },\n daysIn: daysInMonthOf(itemCal.year, itemCal.month),\n cal: itemCal,\n };\n}\n\nconst outOfValidity = (r: TafReport, at: TafExpandAt): boolean => {\n const v = r.validity;\n if (v === undefined || r.nil === true || r.cancelled === true) return false;\n const atAbs = absDayHour(at.day, at.hour) + at.minute;\n return (\n atAbs < absDayHour(v.startDay, v.startHour) ||\n atAbs >= absDayHour(v.endDay < v.startDay ? v.endDay + 31 : v.endDay, v.endHour)\n );\n};\n\n/** 单站展开视觉态(首建与换时刻共用——tier/摘要/提示单一来源) */\nfunction tafMarkerState(\n item: TafLayerItem,\n at: TafExpandAt,\n anchor: TafMonthAnchor,\n locale: \"zh\" | \"en\",\n): { tier: ConditionTier; label: string; summary: string; notes: string[] } {\n const r = item.report;\n const name = item.title ?? r.station;\n const noTimeline = r.nil === true || r.cancelled === true;\n const v = r.validity;\n let tier: ConditionTier = \"unknown\";\n let summary = r.nil === true ? \"缺报(NIL)\" : r.cancelled === true ? \"预报取消(CNL)\" : \"\";\n const notes: string[] = [];\n if (!noTimeline && v !== undefined && outOfValidity(r, at)) {\n // 出窗:灰 unknown(签派复测 N2——「无有效预报」本身是运行信息;卡内有对应出界提示行)\n const early = absDayHour(at.day, at.hour) < absDayHour(v.startDay, v.startHour);\n notes.push(early ? \"预报尚未生效(按发布时基况显示)\" : \"预报已过期(按末段显示)\");\n return {\n tier: \"unknown\",\n label: `${name} · 预报${early ? \"未生效\" : \"已过期\"}`,\n summary,\n notes,\n };\n }\n if (!noTimeline && v !== undefined) {\n const expansion = expandTaf(r, at, anchor);\n // 发作窗内档位/摘要按「主导段 + TEMPO 叠加」合成态(实测批:雷雨发作窗圆点不升档=\n // 图上永远看不到危险窗——叠加合并式与下方提示语同一份,单一来源不漂移)\n const effective =\n expansion.tempo !== undefined\n ? {\n ...expansion.conditions,\n ...expansion.tempo.conditions,\n weather: expansion.tempo.conditions.weather ?? [],\n cavok: expansion.tempo.conditions.cavok,\n }\n : expansion.conditions;\n tier = conditionOf(asConditionInput(effective, false));\n summary = summarizeTaf(effective, locale);\n if (expansion.uncertain)\n notes.push(\n locale === \"en\" ? \"Transition band — timing uncertain\" : \"过渡带(变化时刻不确定)\",\n );\n if (expansion.tempo !== undefined) {\n const tempoSummary = summarizeTaf(effective, locale);\n notes.push((locale === \"en\" ? \"TEMPO bursts: \" : \"TEMPO 发作可能:\") + tempoSummary);\n }\n } else if (v !== undefined) {\n notes.push(`有效期 ${v.raw}`);\n }\n return {\n tier,\n label: `${name} · ${locale === \"en\" ? \"Forecast \" : \"预报\"}${TIER_WORDS[locale][tier]}`,\n summary,\n notes,\n };\n}\n\n/** 点位 divIcon(含常显 ICAO 站码标签)与悬停 tooltip 内容 */\nfunction tafMarkerIcon(\n L: typeof import(\"leaflet\"),\n item: TafLayerItem,\n state: { tier: ConditionTier; label: string; summary: string; notes: string[] },\n): { icon: Leaflet.DivIcon; tip: HTMLElement } {\n const name = item.title ?? item.report.station;\n const marker = L.divIcon({\n className: \"mw-cond-icon\",\n html: `<span role=\"img\" aria-label=\"${escapeHtml(state.label)}\" class=\"mw-dot mw-dot-${state.tier}\" style=\"display:inline-block;width:12px;height:12px;border-radius:50%;background:${TIER_COLORS[state.tier]};border:2px solid #fff;box-shadow:0 0 2px rgba(0,0,0,.4)\"></span><span class=\"mw-code-label\" aria-hidden=\"true\">${escapeHtml(item.report.station)}</span>`,\n iconSize: [14, 14],\n iconAnchor: [7, 7],\n });\n return { icon: marker, tip: tafTipContent(name, state) };\n}\n\nfunction tafTipContent(name: string, state: { summary: string; notes: string[] }): HTMLElement {\n const tip = document.createElement(\"span\");\n tip.append(textCarrier(name));\n if (state.summary !== \"\") tip.append(document.createElement(\"br\"), textCarrier(state.summary));\n for (const n of state.notes) tip.append(document.createElement(\"br\"), textCarrier(n));\n return tip;\n}\n\n/** TAF 标记构建核心:addTafLayer 首建用(标记构建 + 常显站码 + 惰性弹窗) */\nasync function populateTafLayer(\n map: Leaflet.Map,\n group: Leaflet.LayerGroup,\n items: readonly TafLayerItem[],\n options: AddTafLayerOptions,\n L: typeof import(\"leaflet\"),\n): Promise<void> {\n const fallbackDaysIn = options.anchorDays ?? 31;\n const layerCal = options.calendarAnchor;\n const locale = options.locale ?? \"zh\";\n if (layerCal !== undefined) tafLayerCalendar.set(group, layerCal); // 月界批:层连续序 at 的基准月\n tafLayerCard.set(group, options.card ?? {}); // 可变 card 覆盖的初值(setTafLayerTime 带 card 时合并更新)\n for (const item of items) {\n // 月界批:显式 at =层连续序(calendarAnchor 基)→ 归一到本报锚月;缺省各站自起点(报锚内原值)\n const norm = stationNorm(item, options.at, layerCal, fallbackDaysIn);\n const state = tafMarkerState(item, norm.at, { daysIn: norm.daysIn }, locale);\n const { icon, tip } = tafMarkerIcon(L, item, state);\n const marker = L.marker(item.position, { icon });\n tafMarkerItems.set(marker, item);\n marker.bindTooltip(tip);\n\n if (options.popup ?? true) {\n // 惰性弹窗(评测工程 P2-1):占位 DOM 只在 popupopen 时换真卡——滑杆换时刻不清层,重开即见新时刻卡;\n // maxWidth 480 = 卡片设计宽(加宽指令,renderTafCard max-width 同步)\n marker.bindPopup(document.createElement(\"div\"), { maxWidth: 480, ...options.popupOptions });\n // 刷新函数(复测 N1/N2):按层当前时刻重展开取 notes(置顶提示随换时刻更新,与 tooltip 同源),\n // 只重建卡片内容不动焦点——焦点移入仅发生在真实 popupopen(键盘拖滑杆不再被抢焦);\n // card 选项读层的可变覆盖(tafLayerCard)而非建层闭包——切时区后已开弹窗即时换内容();\n // 报文数据面同样现读 item.report(方案B:宿主原位换报——如按查看时刻切换上一周期在效报——\n // 换报后已开弹窗即时跟随新报,捕获建层时的 r/noTimeline 会停在旧报);\n // 月界批:层连续序 current 归一到本报锚月再喂展开/渲染(跨月报池各报正确),card 注入本月锚\n const refresh = (popup: Leaflet.Popup): void => {\n const rNow = item.report;\n const noTimelineNow = rNow.nil === true || rNow.cancelled === true;\n // 月界批:层连续序 currentCont 归一到本报锚月再喂展开/渲染(跨月报池各报正确),card 注入本月锚;\n // 层尚无统一时刻(未传 at)→ 各站自身起点(报锚内原值)\n const currentCont = tafLayerAt.get(group);\n const normNow = stationNorm(\n item,\n noTimelineNow ? undefined : currentCont,\n tafLayerCalendar.get(group),\n fallbackDaysIn,\n );\n const fresh = tafMarkerState(item, normNow.at, { daysIn: normNow.daysIn }, locale);\n const cardOpts: RenderTafCardOptions = {\n locale,\n raw: true,\n ...(currentCont === undefined || noTimelineNow ? {} : { at: normNow.at }),\n ...tafLayerCard.get(group),\n };\n if (normNow.cal !== undefined) cardOpts.monthAnchor = normNow.cal;\n if (cardOpts.stationTitle === undefined && item.title !== undefined) {\n const stationName = item.title.startsWith(`${rNow.station} `)\n ? item.title.slice(rNow.station.length + 1)\n : undefined;\n if (stationName !== undefined) cardOpts.stationTitle = stationName;\n }\n const card = renderTafCard(rNow, cardOpts);\n // 卡内限高滚动()的滚轮隔离由 Leaflet 弹窗内建 disableScrollPropagation(contentNode)\n // 提供(只截传播不拦默认滚动——实测勿再叠加自带监听:纯冗余);行为锁见 index.test.ts C14\n if (fresh.notes.length > 0) {\n const lead = document.createElement(\"p\");\n lead.style.margin = \"0 0 4px\";\n lead.className = \"mw-taf-meta\";\n for (const [i, n] of fresh.notes.entries()) {\n if (i > 0) lead.append(document.createElement(\"br\"));\n lead.append(textCarrier(n));\n }\n card.prepend(lead);\n }\n popup.setContent(card);\n };\n tafPopupRefresh.set(marker, refresh);\n marker.on(\"popupopen\", (e) => {\n if (e.popup === undefined) return;\n marker.closeTooltip();\n refresh(e.popup);\n const focusTarget = e.popup\n .getElement()\n ?.querySelector<HTMLElement>(\"a.leaflet-popup-close-button, button, [href]\");\n focusTarget?.focus();\n });\n }\n marker.addTo(group);\n }\n if (!escapeBoundMaps.has(map)) {\n escapeBoundMaps.add(map);\n map.getContainer().addEventListener(\"keydown\", (event) => {\n if (event.key === \"Escape\") map.closePopup();\n });\n }\n injectTafLayerStyle();\n bindCodeZoom(map);\n // 层级当前时刻:仅显式 at 时设全局态;缺省各站自有效期起(弹窗回退 marker 自身 at)\n if (options.at !== undefined) tafLayerAt.set(group, options.at);\n}\n\n/**\n * 单站某时刻的档位(数据直读,2026-09-24 评测批3#14):与地图圆点同一判据管线\n * (含出窗灰、TEMPO 发作窗升档、NIL/CNL 灰)——宿主面板/对比基准不再从 marker DOM\n * className 正则回读(状态经渲染产物回流的工程债收口),DOM 只做展示。\n * at 语义同 addTafLayer:calendarAnchor 在位时为层连续序(逐报归一),否则为报锚内日号。\n */\n/**\n * METAR 档位判据公共面(conditionOf 薄包装,tafTierOf 同款):实况面板/图例等消费方\n * 与圆点同一判据管线(数据直读,不从 DOM 回流——批3#14 口径)。\n */\nexport function metarTierOf(report: MetarReport): ConditionTier {\n return conditionOf(report);\n}\n\n/** METAR 要素摘要公共面(summarizeReport 薄包装):「2500m +TSRA BKN030CB」式扫视摘要。 */\nexport function summarizeMetarConditions(report: MetarReport, locale: \"zh\" | \"en\"): string {\n return summarizeReport(report, locale);\n}\n\nexport function tafTierOf(\n item: TafLayerItem,\n at: TafExpandAt,\n options: { anchorDays?: number; calendarAnchor?: TafCalendarAnchor } = {},\n): ConditionTier {\n const norm = stationNorm(item, at, options.calendarAnchor, options.anchorDays ?? 31);\n return tafMarkerState(item, norm.at, { daysIn: norm.daysIn }, \"zh\").tier;\n}\n\n// ---------------------------------------------------------------- TAF 时间轴(v0.2 渲染层③:全图统一时刻)\n\n/**\n * Re-expand every TAF marker at a new instant — in-place icon/tooltip update, no rebuild.\n * 全图统一换时刻(评测工程 P2-1 瘦身版):各标记原地 setIcon/setTooltipContent,不再清层重建——\n * 换时刻不销毁已开弹窗(打开中的弹窗即时换内容)、无 DOM/监听器 churn(38 站滑杆拖动不再百卡重建)。\n * 展开是纯函数、遍历同步,无 clearLayers/populate 竞态窗口(旧代际令牌机制随重建路径一并退役)。\n */\nexport async function setTafLayerTime(\n map: Leaflet.Map,\n layer: Leaflet.LayerGroup,\n items: readonly TafLayerItem[],\n options: AddTafLayerOptions = {},\n): Promise<Leaflet.LayerGroup> {\n void map;\n void items; // 签名保留(公开 API 契约):原地更新路径经 markerItems 拿数据,不再需要整表\n const L = await loadLeaflet();\n const fallbackDaysIn = options.anchorDays ?? 31;\n const locale = options.locale ?? \"zh\";\n if (options.calendarAnchor !== undefined) tafLayerCalendar.set(layer, options.calendarAnchor);\n // 缺省 at:保持层当前时刻(不回退到非法 0 日——复测 N5;层尚无时刻时退各站自身有效期起点)\n const at = options.at ??\n tafLayerAt.get(layer) ?? {\n day: items[0]?.report.validity?.startDay ?? 1,\n hour: items[0]?.report.validity?.startHour ?? 0,\n minute: 0,\n };\n if (options.at !== undefined) tafLayerAt.set(layer, options.at);\n // card 覆盖合并(时区单制):带 card 即更新层的可变覆盖并刷新已开弹窗;不带(滑杆换时刻)不回退\n if (options.card !== undefined) {\n tafLayerCard.set(layer, { ...tafLayerCard.get(layer), ...options.card });\n }\n const openRefresh: Leaflet.Marker[] = [];\n layer.eachLayer((ml) => {\n if (!(ml instanceof L.Marker)) return; // 层内非 marker(弹窗代理等)跳过\n const marker: Leaflet.Marker = ml;\n const item = tafMarkerItems.get(marker);\n if (item === undefined) return;\n // 月界批:显式/既有层连续序 at 归一到本报锚月(缺省回退各站自身有效期起点,报锚内原值)\n const norm = stationNorm(item, at, tafLayerCalendar.get(layer), fallbackDaysIn);\n const state = tafMarkerState(item, norm.at, { daysIn: norm.daysIn }, locale);\n const { icon, tip } = tafMarkerIcon(L, item, state);\n marker.setIcon(icon);\n marker.setTooltipContent(tip);\n if (marker.isPopupOpen()) openRefresh.push(marker);\n });\n // 已开弹窗即时换内容:走刷新函数(不动焦点——键盘拖滑杆不被抢焦,复测 N1;notes 按新时刻重算,N2)\n for (const marker of openRefresh) {\n const popup = marker.getPopup();\n if (popup === undefined) continue;\n tafPopupRefresh.get(marker)?.(popup);\n }\n return layer;\n}\n\n/** 时刻展示串(控件与卡片共用口径;en 无「日」字) */\nconst fmtTafAt = (at: TafExpandAt, locale: \"zh\" | \"en\" = \"zh\"): string =>\n locale === \"zh\"\n ? `${String(at.day).padStart(2, \"0\")}日 ${String(at.hour).padStart(2, \"0\")}:${String(at.minute).padStart(2, \"0\")}Z`\n : `Day ${String(at.day).padStart(2, \"0\")} ${String(at.hour).padStart(2, \"0\")}:${String(at.minute).padStart(2, \"0\")} Z`;\n\n/** 控件时刻显示·本地时(时区单制):tag+M月D日HH:MM——月位显式(2026-09-24 评测 P1 月界批):\n * calendarAnchor 在位时走真实月历(at 为自锚月 1 日起的连续日序,day>31 按进位恒正确、跨月不回绕);\n * 缺席时按 31 天折回(残余近似仅显示位:无月语境无从判读真实月份,控件值本身不受影响) */\nconst fmtTafZone = (\n at: TafExpandAt,\n offset: number,\n tag: string,\n cal?: TafCalendarAnchor,\n): string => {\n if (cal !== undefined && at.day >= 1) {\n // 日号超锚月长度(如 9 月锚的 31)=连续序进位(10 月 1 日),Date.UTC 自动进位恒正确\n //(月吻合守卫曾把进位误判病态回退折回——2026-09-24 复验收口;电码日号不超当月,无需守卫)\n const z = new Date(\n Date.UTC(cal.year, cal.month - 1, at.day, at.hour, at.minute) + offset * 60_000,\n );\n return `${tag}${z.getUTCMonth() + 1}月${z.getUTCDate()}日${String(z.getUTCHours()).padStart(2, \"0\")}:${String(z.getUTCMinutes()).padStart(2, \"0\")}`;\n }\n const total = (at.day - 1) * 1440 + at.hour * 60 + at.minute + offset;\n const d = (Math.floor(total / 1440) % 31) + 1;\n const h = Math.floor((total % 1440) / 60);\n const m = total % 60;\n return `${tag}${String(d).padStart(2, \"0\")}日${String(h).padStart(2, \"0\")}:${String(m).padStart(2, \"0\")}`;\n};\n\n/** TafExpandAt ↔ 绝对分钟序(窗/刻度格计算共用;day-1 基准使跨日差值可直接加减) */\nconst tafAbsOf = (at: TafExpandAt): number => (at.day - 1) * 1440 + at.hour * 60 + at.minute;\nconst tafAtOfAbs = (abs: number): TafExpandAt => ({\n day: Math.floor(abs / 1440) + 1,\n hour: Math.floor((abs % 1440) / 60),\n minute: abs % 60,\n});\n\nexport interface TafTimeControlOptions {\n /** 受控图层与数据(每次拨动全量重展开) */\n layer: Leaflet.LayerGroup;\n items: readonly TafLayerItem[];\n /** addTafLayer 的其余选项(locale/anchorDays/popup/card) */\n layerOptions?: Omit<AddTafLayerOptions, \"at\">;\n /** 步进分钟数(滑杆一格),缺省 60 */\n stepMinutes?: number;\n /** 滑杆零点时刻;缺省自动取各站最早有效期起点 */\n from?: TafExpandAt;\n /** 滑杆终点时刻;缺省自动取各站最晚有效期止(评测共识⑤:滑杆窗对齐数据,不再盲拖出界) */\n to?: TafExpandAt;\n /** 轴内刻度间隔(分钟;缺省=仅两端起止标注)。刻度对齐整点(自窗内首个对齐刻度起),\n * 日界(展示时区的 00 时)标 dd日,标签随展示时区单制——底部时间轴批 */\n tickEveryMinutes?: number;\n /** 显示语言(缺省 zh;en 不加「日」字与本地时——评测工程 P2-3 i18n 漏网) */\n locale?: \"zh\" | \"en\";\n /** 展示时区偏移(分钟)——单制:缺省 null=UTC 单制;zh 传 480=北京时单制(标签与两端标注同随) */\n utcOffsetMinutes?: number | null;\n /** 日历月锚(2026-09-24 评测 P1 月界批):滑杆零点所在真实年月(month 1–12)——from/to/at 视为\n * 自该月 1 日起的连续日序(day 可超月长),北京时标签走真实月历、跨月不回绕;缺省 31 天折回显示 */\n calendarAnchor?: TafCalendarAnchor;\n /** 初始时刻(缺省=滑杆零点);时区切换等重建控件场景用来保住当前拨动位置(落到最近格) */\n initialAt?: TafExpandAt;\n /** 时刻变更回调(拿到当前时刻,供宿主联动外部 UI) */\n onTime?: (at: TafExpandAt) => void;\n}\n\n/**\n * A framework-free time-scrub control element for a TAF layer: one range input drives every\n * station's re-expansion (renderer layer ③). Host mounts it anywhere; pure DOM, no Leaflet control\n * inheritance required.\n * TAF 图层的时间滑杆控件(零框架纯 DOM):一个 range 输入驱动全图各站重展开;宿主自行挂载定位。\n */\nexport function createTafTimeControl(\n map: Leaflet.Map,\n options: TafTimeControlOptions,\n): HTMLElement {\n const step = options.stepMinutes ?? 60;\n const locale = options.locale ?? \"zh\";\n const ltOffset = options.utcOffsetMinutes ?? null; // 单制:缺省 UTC(旧 zh 缺省京时括注退役)\n const cal = options.calendarAnchor; // 月界批:真月历锚(缺省 31 天折回显示)\n const zoneText = (at: TafExpandAt): string =>\n ltOffset !== null && locale === \"zh\"\n ? fmtTafZone(at, ltOffset, \"北京时\", cal)\n : fmtTafAt(at, locale);\n const box = document.createElement(\"div\");\n box.className = \"mw-taf-timectrl\";\n box.style.cssText =\n \"display:flex;gap:8px;align-items:center;padding:6px 10px;background:#fff;border:1px solid #d8dee6;border-radius:8px;font:12px/1.4 system-ui,sans-serif;color:#1c2733\";\n const label = document.createElement(\"span\"); // 读屏走 input 的 aria-valuetext(复测 N6:双通道会逐格双朗读)\n const input = document.createElement(\"input\");\n input.type = \"range\";\n input.min = \"0\";\n input.step = \"1\";\n input.setAttribute(\"aria-label\", locale === \"en\" ? \"forecast time\" : \"预报时刻\");\n // 滑杆零点/终点:显式指定 > 各站最早起点/最晚止点(评测共识⑤:窗对齐数据;止点按各自报文回绕归一到绝对序再取最大)\n const from: TafExpandAt =\n options.from ??\n options.items.reduce<TafExpandAt>(\n (acc, it) => {\n const v = it.report.validity;\n if (v === undefined) return acc;\n const cand = { day: v.startDay, hour: v.startHour, minute: 0 };\n if (acc.day === 0 && acc.hour === 0) return cand;\n return cand.day < acc.day || (cand.day === acc.day && cand.hour < acc.hour) ? cand : acc;\n },\n { day: 0, hour: 0, minute: 0 },\n );\n let toAbsMax: number | null = null;\n for (const it of options.items) {\n const v = it.report.validity;\n if (v === undefined) continue;\n const endDay = v.endDay < v.startDay ? v.endDay + 31 : v.endDay;\n const abs = (endDay - 1) * 1440 + v.endHour * 60;\n if (toAbsMax === null || abs > toAbsMax) toAbsMax = abs;\n }\n const fromAbs = tafAbsOf(from);\n // 显式 to 优先(时间轴批:宿主自定「现在+24h」窗)——此前该选项有文档无接线,静默忽略违不静默纪律,本批修\n const toAbs =\n options.to !== undefined ? tafAbsOf(options.to) : (toAbsMax ?? fromAbs + 100 * step);\n const spanSteps = Math.max(1, Math.round((toAbs - fromAbs) / step));\n input.max = String(spanSteps);\n const atOfValue = (value: number): TafExpandAt => {\n const base = from.day * 1440 + from.hour * 60 + from.minute + value * step;\n return {\n day: Math.floor(base / 1440),\n hour: Math.floor((base % 1440) / 60),\n minute: base % 60,\n };\n };\n const apply = (at: TafExpandAt): void => {\n const text = zoneText(at);\n label.textContent = text;\n input.setAttribute(\"aria-valuetext\", text); // 读屏不朗读裸格值(评测工程 P2-3)\n void setTafLayerTime(map, options.layer, options.items, { ...options.layerOptions, at });\n options.onTime?.(at);\n };\n // rAF 合帧(评测工程 P2-1):拖动的高频 input 每帧至多一次全图更新\n let rafPending = false;\n input.addEventListener(\"input\", () => {\n if (rafPending) return;\n rafPending = true;\n requestAnimationFrame(() => {\n rafPending = false;\n apply(atOfValue(Number(input.value)));\n });\n });\n // 初始位置:缺省第 0 格;initialAt 落到最近格(时区切换重建控件不丢当前时刻)\n const idxOf = (at: TafExpandAt): number =>\n Math.max(\n 0,\n Math.min(\n spanSteps,\n Math.round(\n (at.day * 1440 +\n at.hour * 60 +\n at.minute -\n (from.day * 1440 + from.hour * 60 + from.minute)) /\n step,\n ),\n ),\n );\n input.value = String(idxOf(options.initialAt ?? from));\n apply(atOfValue(Number(input.value)));\n box.append(input, label);\n if (options.tickEveryMinutes !== undefined) {\n // 轴内刻度(底部时间轴批):对齐整点的等距标注(自窗内首个对齐刻度起),\n // 日界(展示时区 00 时)标 dd日、其余标 HH:MM,随展示时区单制;终点恒标注(right 锚防溢出)\n const every = Math.max(step, options.tickEveryMinutes);\n const first = Math.ceil((fromAbs + 1) / every) * every;\n const total = toAbs - fromAbs;\n const axis = document.createElement(\"div\");\n axis.style.cssText =\n \"position:relative;height:15px;margin-top:4px;width:100%;font-size:10px;color:#6b7785\";\n /** 刻度短标签:整点 HH:00;展示时区 00 时=日界 M月D日(calendarAnchor 真月历,月界批) */\n const tickText = (at: TafExpandAt): string => {\n if (ltOffset !== null && locale === \"zh\") {\n if (cal !== undefined && at.day >= 1) {\n const z = new Date(\n Date.UTC(cal.year, cal.month - 1, at.day, at.hour, at.minute) + ltOffset * 60_000,\n );\n return z.getUTCHours() === 0 && z.getUTCMinutes() === 0\n ? `北京时${z.getUTCMonth() + 1}月${z.getUTCDate()}日`\n : `北京时${String(z.getUTCHours()).padStart(2, \"0\")}:${String(z.getUTCMinutes()).padStart(2, \"0\")}`;\n }\n const z = tafAtOfAbs(tafAbsOf(at) + ltOffset);\n const day = ((z.day - 1) % 31) + 1; // 无锚折回(残余近似仅显示位,见 fmtTafZone 注释)\n return z.hour === 0 && z.minute === 0\n ? `北京时${String(day).padStart(2, \"0\")}日`\n : `北京时${String(z.hour).padStart(2, \"0\")}:${String(z.minute).padStart(2, \"0\")}`;\n }\n return at.hour === 0 && at.minute === 0\n ? `${String(at.day).padStart(2, \"0\")}日`\n : `${String(at.hour).padStart(2, \"0\")}:${String(at.minute).padStart(2, \"0\")}`;\n };\n const addTick = (abs: number, anchorRight = false): void => {\n const s = document.createElement(\"span\");\n s.textContent = tickText(tafAtOfAbs(abs));\n s.style.cssText = anchorRight\n ? \"position:absolute;right:0;white-space:nowrap\"\n : `position:absolute;left:${(((abs - fromAbs) / total) * 100).toFixed(3)}%;transform:translateX(-50%);white-space:nowrap`;\n axis.append(s);\n };\n // 与终点相距不足一格的尾刻度跳过(防标签相撞)\n for (let a = first; a <= toAbs - every / 2; a += every) addTick(a);\n addTick(toAbs, true);\n box.append(axis);\n return box;\n }\n // 端点标注(复测小白#11:无刻度无范围的「盲拖」——两端起止时刻随展示时区单制,10px 灰字通栏)\n const ticks = document.createElement(\"div\");\n ticks.style.cssText =\n \"display:flex;justify-content:space-between;width:100%;font-size:10px;color:#6b7785\";\n const tickL = document.createElement(\"span\");\n tickL.textContent = zoneText(from);\n const tickR = document.createElement(\"span\");\n tickR.textContent = zoneText(atOfValue(spanSteps));\n ticks.append(tickL, tickR);\n box.append(ticks);\n return box;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metweave/leaflet",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Put parsed METAR/SPECI reports on a Leaflet map: markers, tooltips and report-card popups in one call — bring your own tiles.",
5
5
  "keywords": [
6
6
  "leaflet",
@@ -47,13 +47,13 @@
47
47
  "access": "public"
48
48
  },
49
49
  "dependencies": {
50
- "@metweave/core": "0.1.2",
51
- "@metweave/render": "0.1.2"
50
+ "@metweave/core": "0.2.1",
51
+ "@metweave/render": "0.2.1",
52
+ "@metweave/parser": "0.2.1"
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/leaflet": "^1.9.22",
55
- "leaflet": "^1.9.4",
56
- "@metweave/parser": "0.1.2"
56
+ "leaflet": "^1.9.4"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "leaflet": "^1.9.0"