clxx 3.0.2 → 3.0.4

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 (45) hide show
  1. package/AGENTS.md +49 -3
  2. package/build/Alert/style.js +15 -14
  3. package/build/CarouselNotice/style.js +2 -1
  4. package/build/CitySelect/style.js +40 -39
  5. package/build/Container/index.js +21 -1
  6. package/build/DatePicker/style.d.ts +1 -1
  7. package/build/DatePicker/style.js +25 -24
  8. package/build/Indicator/index.js +2 -1
  9. package/build/Loading/Wrapper.js +3 -2
  10. package/build/Loading/style.js +7 -6
  11. package/build/MapLocationSelection/buildSelectedLocation.d.ts +16 -0
  12. package/build/MapLocationSelection/buildSelectedLocation.js +123 -0
  13. package/build/MapLocationSelection/createProvider.d.ts +8 -0
  14. package/build/MapLocationSelection/createProvider.js +33 -0
  15. package/build/MapLocationSelection/getLocation.d.ts +8 -0
  16. package/build/MapLocationSelection/getLocation.js +112 -0
  17. package/build/MapLocationSelection/index.d.ts +16 -0
  18. package/build/MapLocationSelection/index.js +985 -0
  19. package/build/MapLocationSelection/loader.amap.d.ts +48 -0
  20. package/build/MapLocationSelection/loader.amap.js +125 -0
  21. package/build/MapLocationSelection/loader.bmap.d.ts +8 -0
  22. package/build/MapLocationSelection/loader.bmap.js +60 -0
  23. package/build/MapLocationSelection/provider.amap.d.ts +38 -0
  24. package/build/MapLocationSelection/provider.amap.js +659 -0
  25. package/build/MapLocationSelection/provider.bmap.d.ts +36 -0
  26. package/build/MapLocationSelection/provider.bmap.js +837 -0
  27. package/build/MapLocationSelection/provider.d.ts +45 -0
  28. package/build/MapLocationSelection/provider.js +10 -0
  29. package/build/MapLocationSelection/style.d.ts +4 -0
  30. package/build/MapLocationSelection/style.js +442 -0
  31. package/build/MapLocationSelection/types.d.ts +29 -0
  32. package/build/MapLocationSelection/types.js +22 -0
  33. package/build/MapLocationSelection/userMarker.d.ts +2 -0
  34. package/build/MapLocationSelection/userMarker.js +95 -0
  35. package/build/RegionPicker/style.js +34 -33
  36. package/build/ScrollView/style.js +5 -4
  37. package/build/Toast/style.js +6 -5
  38. package/build/index.d.ts +3 -0
  39. package/build/index.js +8 -2
  40. package/build/utils/rem.d.ts +1 -0
  41. package/build/utils/rem.js +48 -0
  42. package/package.json +2 -2
  43. package/test/src/index/index.jsx +5 -0
  44. package/test/src/index.jsx +1 -0
  45. package/test/src/map-location-selection/index.jsx +192 -0
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ // 把 reverseGeocode 结果 + 候选 POI 拼装成对外的 SelectedLocation。
3
+ //
4
+ // 单一信源:MapLocationSelection 组件的「确定按钮 onSelect」、列表头部
5
+ // 「当前位置」虚拟项、独立的 `getLocation()` 函数式 API 全部走这里。
6
+ // 之前 handleConfirm 与 currentItem useMemo 各自维护一份逐字对齐的内联
7
+ // 实现(用注释「STAY-IN-SYNC 双源同步」标注),任何一边改动都得手动同步
8
+ // 另一边——抽到这里后变成单点维护,从根源上消除「列表第一条显示什么 ≠
9
+ // 确定后提交什么」的体感错位风险。
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.NEAREST_POI_FALLBACK_RADIUS_M = void 0;
12
+ exports.splitAdcode = splitAdcode;
13
+ exports.buildSelectedLocation = buildSelectedLocation;
14
+ // nearestPoi 的距离阈值:80m 覆盖典型楼栋 / 出入口 / 地铁站口的
15
+ // 距离半径,再远就不能代表「用户在地图上选的那个位置」。
16
+ //
17
+ // 用途:当用户没主动点列表项时,借 poiList[0](最近 POI)的 name / address
18
+ // 作为兜底——避免高德 / 百度 reverseGeocode 在没楼宇位置 fallback 到
19
+ // township 粗粒度的"陆家嘴街道""高桥镇"之类。
20
+ exports.NEAREST_POI_FALLBACK_RADIUS_M = 80;
21
+ // 国标 GB/T 2260 编码切分:6 位 adcode → 省(前 2 + "0000")/ 市(前 4
22
+ // + "00")/ 区(完整 6 位)。
23
+ //
24
+ // SDK 直接给 6 位 adcode(高德 r.addressComponent.adcode、百度
25
+ // result.addressComponents.adcode),切分一次就能得到三层 code,**完全
26
+ // 不需要外部映射表**(如 RegionPicker/data.ts)。
27
+ //
28
+ // Corner case:
29
+ // - 不设区的地级市(东莞 441900、中山 442000、嘉峪关 620200 等):
30
+ // cityCode === districtCode,是国标允许的预期结果;
31
+ // - 直辖市:cityCode 是市辖区码(如 110100),不是 110000;provinceCode
32
+ // 才是 110000;
33
+ // - 输入非法(undefined / 不是 6 位数字):返回 undefined,由调用方把
34
+ // 三个 *Code 字段都置 undefined。
35
+ function splitAdcode(adcode) {
36
+ if (!adcode || !/^\d{6}$/.test(adcode))
37
+ return undefined;
38
+ return {
39
+ provinceCode: `${adcode.slice(0, 2)}0000`,
40
+ cityCode: `${adcode.slice(0, 4)}00`,
41
+ districtCode: adcode,
42
+ };
43
+ }
44
+ // 拼装优先级链(与原 handleConfirm 内联实现 1:1 等价):
45
+ //
46
+ // * name:pickedPoi.name → nearestPoi.name → geo.name → 行政区划兜底
47
+ // → 经纬度兜底。POI 名(楼栋/商铺级)放在最前。
48
+ // * address:geo.address → nearestPoi.address → pickedPoi.address →
49
+ // 行政区划文本兜底 → 经纬度兜底。geo.address 缺省市区前缀
50
+ // 时(如只有"中山路 100 号")自动在前面拼接 adminText。
51
+ // * province / city / district:永远以 geo 为准(POI 经常缺这些字段)。
52
+ // * provinceCode / cityCode / districtCode:geo.adcode 切分(splitAdcode)。
53
+ //
54
+ // 兜底链最尾端(reverseGeocode 彻底失败 + 无 POI 兜底):用经纬度字符串
55
+ // 拼接,保证 name + address 永远不为空,业务方拿到的数据永远可读。
56
+ //
57
+ // 行政区划文本拼接的去重:直辖市 city === province 时只保留省名,避免
58
+ // 出现"上海市上海市浦东新区"这种重复。
59
+ function buildSelectedLocation(center, geo, options = {}) {
60
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
61
+ const { pickedPoi, candidatePoi } = options;
62
+ let nearestPoi = null;
63
+ if (!pickedPoi && candidatePoi) {
64
+ if (typeof candidatePoi.distance === "number" &&
65
+ candidatePoi.distance <= exports.NEAREST_POI_FALLBACK_RADIUS_M) {
66
+ nearestPoi = candidatePoi;
67
+ }
68
+ }
69
+ const nearestPoiName = ((_a = nearestPoi === null || nearestPoi === void 0 ? void 0 : nearestPoi.name) === null || _a === void 0 ? void 0 : _a.trim()) || "";
70
+ const nearestPoiAddress = ((_b = nearestPoi === null || nearestPoi === void 0 ? void 0 : nearestPoi.address) === null || _b === void 0 ? void 0 : _b.trim()) || "";
71
+ let name = ((_c = pickedPoi === null || pickedPoi === void 0 ? void 0 : pickedPoi.name) === null || _c === void 0 ? void 0 : _c.trim()) ||
72
+ nearestPoiName ||
73
+ ((_d = geo === null || geo === void 0 ? void 0 : geo.name) === null || _d === void 0 ? void 0 : _d.trim()) ||
74
+ "";
75
+ let address = ((_e = geo === null || geo === void 0 ? void 0 : geo.address) === null || _e === void 0 ? void 0 : _e.trim()) ||
76
+ nearestPoiAddress ||
77
+ ((_f = pickedPoi === null || pickedPoi === void 0 ? void 0 : pickedPoi.address) === null || _f === void 0 ? void 0 : _f.trim()) ||
78
+ "";
79
+ const province = ((_g = geo === null || geo === void 0 ? void 0 : geo.province) === null || _g === void 0 ? void 0 : _g.trim()) || undefined;
80
+ const city = ((_h = geo === null || geo === void 0 ? void 0 : geo.city) === null || _h === void 0 ? void 0 : _h.trim()) || undefined;
81
+ const district = ((_j = geo === null || geo === void 0 ? void 0 : geo.district) === null || _j === void 0 ? void 0 : _j.trim()) || undefined;
82
+ const adminText = [
83
+ province,
84
+ city && city !== province ? city : "",
85
+ district,
86
+ ]
87
+ .filter((s) => !!s && s.length > 0)
88
+ .join("");
89
+ if (!address) {
90
+ address =
91
+ adminText ||
92
+ `经纬度 ${center[0].toFixed(6)},${center[1].toFixed(6)}`;
93
+ }
94
+ else if (adminText &&
95
+ !address.startsWith(adminText) &&
96
+ !address.includes(adminText)) {
97
+ // address 已有但缺省市区前缀(如只返回"中山路 100 号")→ 在前面补"省市区",
98
+ // 例如:上海市浦东新区 + 中山路 100 号 = "上海市浦东新区中山路 100 号"。
99
+ // 双重检查 startsWith / includes:高德 formattedAddress 有时已经内嵌
100
+ // 省市区前缀,这时不能重复拼接。
101
+ address = `${adminText}${address}`;
102
+ }
103
+ if (!name) {
104
+ name =
105
+ district ||
106
+ city ||
107
+ province ||
108
+ `位置 ${center[0].toFixed(6)},${center[1].toFixed(6)}`;
109
+ }
110
+ const codes = splitAdcode(geo === null || geo === void 0 ? void 0 : geo.adcode);
111
+ return {
112
+ name,
113
+ address,
114
+ longitude: center[0],
115
+ latitude: center[1],
116
+ city,
117
+ province,
118
+ district,
119
+ provinceCode: codes === null || codes === void 0 ? void 0 : codes.provinceCode,
120
+ cityCode: codes === null || codes === void 0 ? void 0 : codes.cityCode,
121
+ districtCode: codes === null || codes === void 0 ? void 0 : codes.districtCode,
122
+ };
123
+ }
@@ -0,0 +1,8 @@
1
+ import type { MapProvider, MapProviderType } from "./provider";
2
+ export interface CreateProviderOptions {
3
+ provider?: MapProviderType;
4
+ amapKey?: string;
5
+ securityJsCode?: string;
6
+ bmapAk?: string;
7
+ }
8
+ export declare function createProvider(opts: CreateProviderOptions): MapProvider;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ // MapProvider 工厂:根据配置选择 amap / bmap 实现并校验必传字段。
3
+ //
4
+ // 抽出位置:原本内联在 index.tsx 的 createProvider,因为 getLocation
5
+ // (独立的纯函数式定位 API,见 getLocation.ts)也需要相同的 provider
6
+ // 创建逻辑——避免「UI 走 amap / 函数式调用走 amap」两条路径出现配置
7
+ // 校验差异(一边漏校 securityJsCode、一边漏报 ak 缺失等)。
8
+ //
9
+ // CreateProviderOptions 也是 MapLocationSelectionProps / GetLocationOptions
10
+ // 的最小公共子集——两边都通过 extends 承接这组字段,调用方传一次配置即可。
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createProvider = createProvider;
13
+ const provider_amap_1 = require("./provider.amap");
14
+ const provider_bmap_1 = require("./provider.bmap");
15
+ function createProvider(opts) {
16
+ var _a;
17
+ const type = (_a = opts.provider) !== null && _a !== void 0 ? _a : "amap";
18
+ if (type === "bmap") {
19
+ if (!opts.bmapAk) {
20
+ throw new Error("[MapLocationSelection] provider=bmap 时 bmapAk 必填");
21
+ }
22
+ return new provider_bmap_1.BMapProvider({
23
+ ak: opts.bmapAk,
24
+ });
25
+ }
26
+ if (!opts.amapKey) {
27
+ throw new Error("[MapLocationSelection] provider=amap 时 amapKey 必填");
28
+ }
29
+ return new provider_amap_1.AMapProvider({
30
+ amapKey: opts.amapKey,
31
+ securityJsCode: opts.securityJsCode,
32
+ });
33
+ }
@@ -0,0 +1,8 @@
1
+ import { type CreateProviderOptions } from "./createProvider";
2
+ import { type SelectedLocation } from "./types";
3
+ export interface GetLocationOptions extends CreateProviderOptions {
4
+ timeout?: number;
5
+ initialCity?: string;
6
+ allowIpFallback?: boolean;
7
+ }
8
+ export declare function getLocation(options?: GetLocationOptions): Promise<SelectedLocation>;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ // 独立的函数式定位 API:不挂组件直接拿到一份 SelectedLocation。
3
+ //
4
+ // 与 MapLocationSelection 组件的关系:
5
+ // * 组件 = 「打开地图 → 用户拖图选位置 → 点确定 → onSelect」交互式流程;
6
+ // * getLocation = 「直接拿一份当前 GPS 位置的 SelectedLocation」无 UI 流程。
7
+ //
8
+ // 数据格式与组件 onSelect 完全一致——两边都通过 buildSelectedLocation 这同
9
+ // 一个 helper 拼装,**不可能出现字段差异**。业务方拿 getLocation 的返回值
10
+ // 与拿组件的 onSelect 回调可以走同一条业务处理路径。
11
+ //
12
+ // 内部依赖:
13
+ // * createProvider:与组件复用同一份配置校验(amapKey / bmapAk 必填检查);
14
+ // * provider.initHeadless:**仅加载 SDK + 创建 service 类,不创建 Map 实例**——
15
+ // 不挂 DOM、不下载瓦片、不渲染。SDK 的 Geolocation / Geocoder /
16
+ // PlaceSearch / LocalSearch 这几个 service 类**本身就独立于 Map**(高德
17
+ // 官方示例直接 new AMap.Geolocation()、百度 LocalSearch 文档明示第一参数
18
+ // 可传 Point/city 而非 Map),所以走 headless 是 SDK 设计支持的,不是 hack;
19
+ // * provider.geolocate:拿 GPS 经纬度(去抖、坐标系自洽、IP 兜底过滤);
20
+ // * provider.reverseGeocode:拿省市区 + 详细地址 + adcode;
21
+ // * provider.searchAround:拿周边 POI(用于 nearestPoi 兜底,避免名字
22
+ // fallback 到"街道镇"粗粒度);
23
+ // * buildSelectedLocation:与组件 handleConfirm 共用的拼装逻辑。
24
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
25
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
26
+ return new (P || (P = Promise))(function (resolve, reject) {
27
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
28
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
29
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
30
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
31
+ });
32
+ };
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.getLocation = getLocation;
35
+ const buildSelectedLocation_1 = require("./buildSelectedLocation");
36
+ const createProvider_1 = require("./createProvider");
37
+ const types_1 = require("./types");
38
+ function getLocation() {
39
+ return __awaiter(this, arguments, void 0, function* (options = {}) {
40
+ var _a;
41
+ const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 15000;
42
+ let provider = null;
43
+ let timer = null;
44
+ try {
45
+ // createProvider 内置必填字段校验(amapKey / bmapAk 缺失立即抛 Error)。
46
+ provider = (0, createProvider_1.createProvider)(options);
47
+ const work = runGetLocation(provider, options);
48
+ if (timeout > 0 && Number.isFinite(timeout)) {
49
+ const timeoutPromise = new Promise((_, reject) => {
50
+ timer = window.setTimeout(() => {
51
+ reject(new Error(`[getLocation] 超时(${timeout}ms)`));
52
+ }, timeout);
53
+ });
54
+ return yield Promise.race([work, timeoutPromise]);
55
+ }
56
+ return yield work;
57
+ }
58
+ finally {
59
+ if (timer !== null) {
60
+ window.clearTimeout(timer);
61
+ timer = null;
62
+ }
63
+ if (provider) {
64
+ try {
65
+ provider.destroy();
66
+ }
67
+ catch (_b) {
68
+ // ignore
69
+ }
70
+ }
71
+ }
72
+ });
73
+ }
74
+ // 真正的工作流。拆出来的目的:让 getLocation 的 try/finally 只负责资源
75
+ // 清理,业务流程在这里更清晰,且方便 Promise.race 包装超时。
76
+ function runGetLocation(provider, options) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ // headless 模式:仅加载 SDK + 创建 service 类,不创建 Map 实例。
79
+ // 详见 provider.initHeadless 接口注释——节省 ~5-10 个瓦片请求 +
80
+ // ~100-300ms 渲染开销,无需挂任何 DOM 容器。
81
+ yield provider.initHeadless({ initialCity: options.initialCity });
82
+ const pos = yield provider.geolocate({
83
+ allowIpFallback: options.allowIpFallback,
84
+ });
85
+ if (!pos) {
86
+ throw new Error("[getLocation] 定位失败:用户拒绝授权 / 浏览器不支持 GPS / 非 https 环境" +
87
+ (options.allowIpFallback
88
+ ? ""
89
+ : "(如需接受 IP 兜底定位,请传 allowIpFallback: true)"));
90
+ }
91
+ // 反查 + 周边搜索并行——总耗时 ≈ max(reverseGeocode, searchAround),
92
+ // 而非串行 sum。两者互不依赖,并行是无成本的体感优化。
93
+ // 周边搜索的目的:拿到 nearestPoi 兜底 name——与组件 handleConfirm 在
94
+ // 「未点列表 + 点确定」分支的兜底链一致(详见 buildSelectedLocation 的
95
+ // pickedPoi / candidatePoi 注释)。
96
+ // pageSize=20 / radius=200 与组件默认值对齐,行为完全等价。
97
+ const [geo, around] = yield Promise.all([
98
+ provider.reverseGeocode(pos).catch(() => null),
99
+ provider
100
+ .searchAround(pos, { page: 1, pageSize: 20, radius: 200 })
101
+ .catch(() => ({ pois: [], hasMore: false })),
102
+ ]);
103
+ // candidatePoi 的 distance 字段口径:组件 UI 层通过 rewriteDistanceFromCenter
104
+ // 在 commit 时把 SDK 自带 distance 重写为「POI ↔ centerPin」(haversine 重算)。
105
+ // getLocation 没有 UI commit 路径,但 buildSelectedLocation 用 distance 字段
106
+ // 判断「是否在 80m 兜底阈值内」——必须保证口径一致,所以这里手动重算一次。
107
+ const top = around.pois[0];
108
+ const candidatePoi = top
109
+ ? Object.assign(Object.assign({}, top), { distance: (0, types_1.haversineMeters)(pos[0], pos[1], top.location.lng, top.location.lat) }) : null;
110
+ return (0, buildSelectedLocation_1.buildSelectedLocation)(pos, geo, { candidatePoi });
111
+ });
112
+ }
@@ -0,0 +1,16 @@
1
+ import { type SelectedLocation } from "./types";
2
+ import { type CreateProviderOptions } from "./createProvider";
3
+ export type { SelectedLocation } from "./types";
4
+ export { getLocation, type GetLocationOptions } from "./getLocation";
5
+ export interface MapLocationSelectionProps extends CreateProviderOptions {
6
+ primary?: string;
7
+ initialCenter?: [number, number];
8
+ initialCity?: string;
9
+ searchRadius?: number;
10
+ pageSize?: number;
11
+ allowIpFallback?: boolean;
12
+ onClose?: () => void;
13
+ onSelect?: (loc: SelectedLocation) => void;
14
+ }
15
+ export declare function MapLocationSelection(props: MapLocationSelectionProps): import("@emotion/react/jsx-runtime").JSX.Element;
16
+ export declare function showMapLocationSelection(props: MapLocationSelectionProps): void;