@zerodep-dev/vue-tsx-pro-mobile 0.0.0-stage → 0.4.0

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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -2
  3. package/dist/area/data.d.ts +10 -0
  4. package/dist/area/data.js +3899 -0
  5. package/dist/area/data.js.map +1 -0
  6. package/dist/area/index.d.ts +22 -0
  7. package/dist/area/index.js +41 -0
  8. package/dist/area/index.js.map +1 -0
  9. package/dist/base/Button.d.ts +170 -0
  10. package/dist/base/Button.js +537 -0
  11. package/dist/base/Button.js.map +1 -0
  12. package/dist/base/Icon.d.ts +38 -0
  13. package/dist/base/Icon.js +65 -0
  14. package/dist/base/Icon.js.map +1 -0
  15. package/dist/capability/PrivacyPrompt.d.ts +1 -0
  16. package/dist/capability/PrivacyPrompt.js +73 -0
  17. package/dist/capability/PrivacyPrompt.js.map +1 -0
  18. package/dist/capability/types.d.ts +92 -0
  19. package/dist/capability/usePermission.d.ts +23 -0
  20. package/dist/capability/usePermission.js +52 -0
  21. package/dist/capability/usePermission.js.map +1 -0
  22. package/dist/capability/useStorage.d.ts +8 -0
  23. package/dist/capability/useStorage.js +19 -0
  24. package/dist/capability/useStorage.js.map +1 -0
  25. package/dist/capability/web.d.ts +18 -0
  26. package/dist/capability/web.js +109 -0
  27. package/dist/capability/web.js.map +1 -0
  28. package/dist/capacitor/index.d.ts +9 -0
  29. package/dist/capacitor/index.js +143 -0
  30. package/dist/capacitor/index.js.map +1 -0
  31. package/dist/config/ConfigProvider.d.ts +37 -0
  32. package/dist/config/ConfigProvider.js +64 -0
  33. package/dist/config/ConfigProvider.js.map +1 -0
  34. package/dist/config/context.d.ts +16 -0
  35. package/dist/config/context.js +20 -0
  36. package/dist/config/context.js.map +1 -0
  37. package/dist/config/lang.d.ts +85 -0
  38. package/dist/config/lang.js +138 -0
  39. package/dist/config/lang.js.map +1 -0
  40. package/dist/display/Badge.d.ts +39 -0
  41. package/dist/display/Badge.js +108 -0
  42. package/dist/display/Badge.js.map +1 -0
  43. package/dist/display/Cell.d.ts +92 -0
  44. package/dist/display/Cell.js +204 -0
  45. package/dist/display/Cell.js.map +1 -0
  46. package/dist/display/Collapse.d.ts +47 -0
  47. package/dist/display/Collapse.js +173 -0
  48. package/dist/display/Collapse.js.map +1 -0
  49. package/dist/display/Empty.d.ts +42 -0
  50. package/dist/display/Empty.js +79 -0
  51. package/dist/display/Empty.js.map +1 -0
  52. package/dist/display/Grid.d.ts +49 -0
  53. package/dist/display/Grid.js +136 -0
  54. package/dist/display/Grid.js.map +1 -0
  55. package/dist/display/Image.d.ts +70 -0
  56. package/dist/display/Image.js +138 -0
  57. package/dist/display/Image.js.map +1 -0
  58. package/dist/display/IndexBar.d.ts +34 -0
  59. package/dist/display/IndexBar.js +158 -0
  60. package/dist/display/IndexBar.js.map +1 -0
  61. package/dist/display/List.d.ts +34 -0
  62. package/dist/display/List.js +105 -0
  63. package/dist/display/List.js.map +1 -0
  64. package/dist/display/NoticeBar.d.ts +57 -0
  65. package/dist/display/NoticeBar.js +172 -0
  66. package/dist/display/NoticeBar.js.map +1 -0
  67. package/dist/display/Steps.d.ts +59 -0
  68. package/dist/display/Steps.js +158 -0
  69. package/dist/display/Steps.js.map +1 -0
  70. package/dist/display/SwipeCell.d.ts +37 -0
  71. package/dist/display/SwipeCell.js +147 -0
  72. package/dist/display/SwipeCell.js.map +1 -0
  73. package/dist/display/Swiper.d.ts +47 -0
  74. package/dist/display/Swiper.js +173 -0
  75. package/dist/display/Swiper.js.map +1 -0
  76. package/dist/display/Tag.d.ts +99 -0
  77. package/dist/display/Tag.js +320 -0
  78. package/dist/display/Tag.js.map +1 -0
  79. package/dist/feedback/Dialog.d.ts +79 -0
  80. package/dist/feedback/Dialog.js +186 -0
  81. package/dist/feedback/Dialog.js.map +1 -0
  82. package/dist/feedback/FeedbackOutlet.d.ts +2 -0
  83. package/dist/feedback/FeedbackOutlet.js +31 -0
  84. package/dist/feedback/FeedbackOutlet.js.map +1 -0
  85. package/dist/feedback/Loading.d.ts +26 -0
  86. package/dist/feedback/Loading.js +68 -0
  87. package/dist/feedback/Loading.js.map +1 -0
  88. package/dist/feedback/Notify.d.ts +48 -0
  89. package/dist/feedback/Notify.js +116 -0
  90. package/dist/feedback/Notify.js.map +1 -0
  91. package/dist/feedback/PullRefresh.d.ts +58 -0
  92. package/dist/feedback/PullRefresh.js +178 -0
  93. package/dist/feedback/PullRefresh.js.map +1 -0
  94. package/dist/feedback/Skeleton.d.ts +56 -0
  95. package/dist/feedback/Skeleton.js +99 -0
  96. package/dist/feedback/Skeleton.js.map +1 -0
  97. package/dist/feedback/Toast.d.ts +89 -0
  98. package/dist/feedback/Toast.js +186 -0
  99. package/dist/feedback/Toast.js.map +1 -0
  100. package/dist/form/Checkbox.d.ts +52 -0
  101. package/dist/form/Checkbox.js +107 -0
  102. package/dist/form/Checkbox.js.map +1 -0
  103. package/dist/form/Field.d.ts +91 -0
  104. package/dist/form/Field.js +296 -0
  105. package/dist/form/Field.js.map +1 -0
  106. package/dist/form/Form.d.ts +50 -0
  107. package/dist/form/Form.js +96 -0
  108. package/dist/form/Form.js.map +1 -0
  109. package/dist/form/FormItem.d.ts +57 -0
  110. package/dist/form/FormItem.js +142 -0
  111. package/dist/form/FormItem.js.map +1 -0
  112. package/dist/form/Radio.d.ts +41 -0
  113. package/dist/form/Radio.js +85 -0
  114. package/dist/form/Radio.js.map +1 -0
  115. package/dist/form/Rate.d.ts +45 -0
  116. package/dist/form/Rate.js +162 -0
  117. package/dist/form/Rate.js.map +1 -0
  118. package/dist/form/Search.d.ts +50 -0
  119. package/dist/form/Search.js +152 -0
  120. package/dist/form/Search.js.map +1 -0
  121. package/dist/form/Slider.d.ts +43 -0
  122. package/dist/form/Slider.js +213 -0
  123. package/dist/form/Slider.js.map +1 -0
  124. package/dist/form/Stepper.d.ts +61 -0
  125. package/dist/form/Stepper.js +222 -0
  126. package/dist/form/Stepper.js.map +1 -0
  127. package/dist/form/Switch.d.ts +33 -0
  128. package/dist/form/Switch.js +116 -0
  129. package/dist/form/Switch.js.map +1 -0
  130. package/dist/form/Uploader.d.ts +67 -0
  131. package/dist/form/Uploader.js +207 -0
  132. package/dist/form/Uploader.js.map +1 -0
  133. package/dist/form/choice.d.ts +42 -0
  134. package/dist/form/choice.js +109 -0
  135. package/dist/form/choice.js.map +1 -0
  136. package/dist/form/context.d.ts +83 -0
  137. package/dist/form/context.js +89 -0
  138. package/dist/form/context.js.map +1 -0
  139. package/dist/index.d.ts +66 -0
  140. package/dist/index.js +59 -0
  141. package/dist/layout/Divider.d.ts +34 -0
  142. package/dist/layout/Divider.js +55 -0
  143. package/dist/layout/Divider.js.map +1 -0
  144. package/dist/layout/Space.d.ts +66 -0
  145. package/dist/layout/Space.js +65 -0
  146. package/dist/layout/Space.js.map +1 -0
  147. package/dist/navigation/BackTop.d.ts +29 -0
  148. package/dist/navigation/BackTop.js +89 -0
  149. package/dist/navigation/BackTop.js.map +1 -0
  150. package/dist/navigation/NavBar.d.ts +46 -0
  151. package/dist/navigation/NavBar.js +141 -0
  152. package/dist/navigation/NavBar.js.map +1 -0
  153. package/dist/navigation/Sticky.d.ts +19 -0
  154. package/dist/navigation/Sticky.js +28 -0
  155. package/dist/navigation/Sticky.js.map +1 -0
  156. package/dist/navigation/TabBar.d.ts +54 -0
  157. package/dist/navigation/TabBar.js +138 -0
  158. package/dist/navigation/TabBar.js.map +1 -0
  159. package/dist/navigation/Tabs.d.ts +45 -0
  160. package/dist/navigation/Tabs.js +206 -0
  161. package/dist/navigation/Tabs.js.map +1 -0
  162. package/dist/overlay/ActionSheet.d.ts +80 -0
  163. package/dist/overlay/ActionSheet.js +164 -0
  164. package/dist/overlay/ActionSheet.js.map +1 -0
  165. package/dist/overlay/Overlay.d.ts +35 -0
  166. package/dist/overlay/Overlay.js +85 -0
  167. package/dist/overlay/Overlay.js.map +1 -0
  168. package/dist/overlay/Popup.d.ts +76 -0
  169. package/dist/overlay/Popup.js +191 -0
  170. package/dist/overlay/Popup.js.map +1 -0
  171. package/dist/picker/Calendar.d.ts +69 -0
  172. package/dist/picker/Calendar.js +213 -0
  173. package/dist/picker/Calendar.js.map +1 -0
  174. package/dist/picker/Cascader.d.ts +50 -0
  175. package/dist/picker/Cascader.js +199 -0
  176. package/dist/picker/Cascader.js.map +1 -0
  177. package/dist/picker/DatePicker.d.ts +52 -0
  178. package/dist/picker/DatePicker.js +135 -0
  179. package/dist/picker/DatePicker.js.map +1 -0
  180. package/dist/picker/DropdownMenu.d.ts +39 -0
  181. package/dist/picker/DropdownMenu.js +242 -0
  182. package/dist/picker/DropdownMenu.js.map +1 -0
  183. package/dist/picker/Picker.d.ts +67 -0
  184. package/dist/picker/Picker.js +185 -0
  185. package/dist/picker/Picker.js.map +1 -0
  186. package/dist/picker/PickerColumn.d.ts +36 -0
  187. package/dist/picker/PickerColumn.js +175 -0
  188. package/dist/picker/PickerColumn.js.map +1 -0
  189. package/dist/theme/dark.d.ts +121 -0
  190. package/dist/theme/dark.js +77 -0
  191. package/dist/theme/dark.js.map +1 -0
  192. package/dist/theme/divider.d.ts +6 -0
  193. package/dist/theme/divider.js +18 -0
  194. package/dist/theme/divider.js.map +1 -0
  195. package/dist/theme/token-names.d.ts +1389 -0
  196. package/dist/theme/tokens.d.ts +549 -0
  197. package/dist/theme/tokens.js +312 -0
  198. package/dist/theme/tokens.js.map +1 -0
  199. package/dist/utils/host.d.ts +97 -0
  200. package/dist/utils/host.js +98 -0
  201. package/dist/utils/host.js.map +1 -0
  202. package/dist/utils/presence.d.ts +11 -0
  203. package/dist/utils/presence.js +34 -0
  204. package/dist/utils/presence.js.map +1 -0
  205. package/dist/utils/props.d.ts +38 -0
  206. package/dist/utils/props.js +115 -0
  207. package/dist/utils/props.js.map +1 -0
  208. package/dist/utils/scales.d.ts +48 -0
  209. package/package.json +106 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Button.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode"],"sources":["../../src/base/Button.tsx"],"sourcesContent":["/**\n * 按钮(button):样式 × 色调 × 尺寸 × 圆角,图标、加载中、占满一行。按下时变深一档(触屏没有悬停)。\n *\n * <Button onClick={save}>保存</Button>\n * <Button variant=\"outline\" tone=\"neutral\" size=\"sm\">取消</Button>\n * <Button tone=\"danger\" icon={Trash2} loading={removing}>删除</Button>\n * <Button size=\"lg\" fullWidth radius=\"full\">提交订单</Button>\n *\n * 加载中与禁用时不响应点击(使用方的 onClick 不会被调用);加载中保持原宽度、不变灰,读屏得知 aria-busy。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { Loading, type LoadingProps } from '../feedback/Loading';\nimport { isMiniProgram, useHost } from '../utils/host';\nimport { dataAttrs, hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport type { MobileRadius, MobileSize, MobileTone } from '../utils/scales';\nimport { Icon, type IconData, type IconProps } from './Icon';\n\n/** 按钮样式 */\nexport interface ButtonVariants {\n /** 实底:一组操作里的主要操作 */\n solid: unknown;\n /** 浅色底:次要但仍需突出的操作 */\n soft: unknown;\n /** 描边:次要操作 */\n outline: unknown;\n /** 无底无边,按下时才有底色:导航栏、列表里的文字按钮 */\n ghost: unknown;\n}\n\n/** 状态与语义属性由组件管理 */\nexport type ButtonManaged = 'disabled' | 'aria-busy' | 'aria-disabled';\n\n/**\n * 小程序按钮的开放能力(原生 button 的 open-type),H5 里不起作用:\n * getPhoneNumber 手机号、chooseAvatar 选头像、agreePrivacyAuthorization 同意隐私协议、share 转发、contact 客服、\n * openSetting 设置页(以上是微信);getAuthorize 授权(支付宝,配合 authScope)\n */\nexport type ButtonOpenType =\n | 'getPhoneNumber'\n | 'chooseAvatar'\n | 'agreePrivacyAuthorization'\n | 'share'\n | 'contact'\n | 'openSetting'\n | 'getAuthorize';\n\n/** 未声明的属性落到 button 上 */\nexport interface ButtonProps extends PartProps<'button', ButtonManaged> {\n /**\n * 样式\n * @default 'solid'\n */\n variant?: keyof ButtonVariants;\n /**\n * 色调\n * @default 'accent'\n */\n tone?: MobileTone;\n /**\n * 尺寸\n * @default 'md'\n */\n size?: MobileSize;\n /**\n * 圆角\n * @default 'md'\n */\n radius?: MobileRadius;\n /** 占满容器宽度 */\n fullWidth?: boolean;\n /** 不可用 */\n disabled?: boolean;\n /** 加载中:图标换成旋转指示,不响应点击 */\n loading?: boolean;\n /** 加载中显示的文字;不给时保留原来的文字 */\n loadingText?: VNodeChild;\n /** 文字前的图标 */\n icon?: IconData;\n /** 文字后的图标 */\n iconEnd?: IconData;\n /** 小程序的开放能力:只能由用户点这个按钮触发的平台能力(手机号、头像、隐私协议同意……) */\n openType?: ButtonOpenType;\n /** 支付宝 getAuthorize 的授权范围:phoneNumber 手机号、userInfo 会员基础信息 */\n authScope?: 'phoneNumber' | 'userInfo';\n /** 开放能力的结果(平台事件的 detail):手机号交给 useHost().phoneNumber 换凭证,头像的 detail.avatarUrl 是临时路径 */\n onOpenType?: (detail: unknown) => void;\n /** 按钮文字 */\n children?: VNodeChild;\n /** 内容层(包着图标与文字的 span)的部件属性 */\n propsContent?: PartProps<'span'>;\n /** 前置图标的部件属性 */\n propsIcon?: Partial<IconProps>;\n /** 后置图标的部件属性 */\n propsIconEnd?: Partial<IconProps>;\n /** 加载指示的部件属性 */\n propsLoading?: Partial<LoadingProps>;\n}\n\n/** Button 的部件 */\nexport type ButtonPart = 'root' | 'content' | 'icon';\n\n/** Button 的配方:样式 × 色调 × 尺寸 × 圆角 */\nexport const buttonRecipe = css.recipe({\n name: 'Button',\n parts: ['root', 'content', 'icon'],\n base: {\n root: {\n boxSizing: 'border-box',\n position: 'relative',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n margin: 0,\n borderWidth: '_xs',\n borderStyle: 'solid',\n borderColor: 'transparent',\n fontFamily: 'inherit',\n fontWeight: '_medium',\n lineHeight: '_tight',\n whiteSpace: 'nowrap',\n textAlign: 'center',\n verticalAlign: 'middle',\n userSelect: 'none',\n cursor: { default: 'pointer', _disabled: 'not-allowed' },\n touchAction: 'manipulation',\n WebkitTapHighlightColor: 'transparent',\n opacity: { default: 1, '[data-disabled]': '_disabled' },\n transitionProperty: 'background-color, border-color, color, opacity',\n transitionDuration: '_xs',\n transitionTimingFunction: '_standard',\n },\n content: { display: 'inline-flex', alignItems: 'center', gap: '_xs' },\n icon: { fontSize: '1.15em' },\n },\n variants: {\n size: {\n xs: { root: { fontSize: '_xs', minHeight: '2em', paddingInline: '_sm' } },\n sm: { root: { fontSize: '_md', minHeight: '2.3em', paddingInline: '_md' } },\n md: { root: { fontSize: '_lg', minHeight: '_control', paddingInline: '_lg' } },\n lg: { root: { fontSize: '_lg', minHeight: '3.125em', paddingInline: '_xl' } },\n },\n radius: {\n none: { root: { borderRadius: '_none' } },\n sm: { root: { borderRadius: '_sm' } },\n md: { root: { borderRadius: '_md' } },\n lg: { root: { borderRadius: '_lg' } },\n full: { root: { borderRadius: '_full' } },\n },\n fullWidth: { true: { root: { display: 'flex', width: '100%' } } },\n variant: {\n solid: {},\n soft: {},\n outline: { root: { backgroundColor: '_surface' } },\n ghost: { root: { backgroundColor: 'transparent' } },\n },\n tone: {\n accent: {},\n neutral: {},\n danger: {},\n success: {},\n warning: {},\n info: {},\n },\n },\n // 按下(:active)变深一档;禁用时不变([data-disabled] 上不加按下的样式)\n compound: [\n // ───── 实底 ─────\n {\n when: { variant: 'solid', tone: 'accent' },\n root: {\n color: '_accentContrast',\n backgroundColor: { default: '_accent', ':active:not([data-disabled])': '_accentActive' },\n },\n },\n {\n when: { variant: 'solid', tone: 'neutral' },\n root: {\n color: '_inverseText',\n backgroundColor: '_inverseSurface',\n opacity: {\n default: 1,\n ':active:not([data-disabled])': 0.85,\n '[data-disabled]': '_disabled',\n },\n },\n },\n {\n when: { variant: 'solid', tone: 'danger' },\n root: {\n color: '_dangerContrast',\n backgroundColor: { default: '_danger', ':active:not([data-disabled])': '_dangerActive' },\n },\n },\n {\n when: { variant: 'solid', tone: 'success' },\n root: {\n color: '_successContrast',\n backgroundColor: { default: '_success', ':active:not([data-disabled])': '_successActive' },\n },\n },\n {\n when: { variant: 'solid', tone: 'warning' },\n root: {\n color: '_warningContrast',\n backgroundColor: { default: '_warning', ':active:not([data-disabled])': '_warningActive' },\n },\n },\n {\n when: { variant: 'solid', tone: 'info' },\n root: {\n color: '_infoContrast',\n backgroundColor: { default: '_info', ':active:not([data-disabled])': '_infoActive' },\n },\n },\n // ───── 浅色底 ─────\n {\n when: { variant: 'soft', tone: 'accent' },\n root: {\n color: '_accentText',\n backgroundColor: {\n default: '_accentSoft',\n ':active:not([data-disabled])': '_accentSoftActive',\n },\n },\n },\n {\n when: { variant: 'soft', tone: 'neutral' },\n root: {\n color: '_text',\n backgroundColor: { default: '_fill', ':active:not([data-disabled])': '_fillActive' },\n },\n },\n {\n when: { variant: 'soft', tone: 'danger' },\n root: {\n color: '_dangerText',\n backgroundColor: {\n default: '_dangerSoft',\n ':active:not([data-disabled])': '_dangerSoftActive',\n },\n },\n },\n {\n when: { variant: 'soft', tone: 'success' },\n root: {\n color: '_successText',\n backgroundColor: {\n default: '_successSoft',\n ':active:not([data-disabled])': '_successSoftActive',\n },\n },\n },\n {\n when: { variant: 'soft', tone: 'warning' },\n root: {\n color: '_warningText',\n backgroundColor: {\n default: '_warningSoft',\n ':active:not([data-disabled])': '_warningSoftActive',\n },\n },\n },\n {\n when: { variant: 'soft', tone: 'info' },\n root: {\n color: '_infoText',\n backgroundColor: {\n default: '_infoSoft',\n ':active:not([data-disabled])': '_infoSoftActive',\n },\n },\n },\n // ───── 描边 ─────\n {\n when: { variant: 'outline', tone: 'accent' },\n root: {\n color: '_accentText',\n borderColor: '_accent',\n backgroundColor: { default: '_surface', ':active:not([data-disabled])': '_accentSoft' },\n },\n },\n {\n when: { variant: 'outline', tone: 'neutral' },\n root: {\n color: '_text',\n borderColor: '_borderStrong',\n backgroundColor: { default: '_surface', ':active:not([data-disabled])': '_fill' },\n },\n },\n {\n when: { variant: 'outline', tone: 'danger' },\n root: {\n color: '_dangerText',\n borderColor: '_danger',\n backgroundColor: { default: '_surface', ':active:not([data-disabled])': '_dangerSoft' },\n },\n },\n {\n when: { variant: 'outline', tone: 'success' },\n root: {\n color: '_successText',\n borderColor: '_success',\n backgroundColor: { default: '_surface', ':active:not([data-disabled])': '_successSoft' },\n },\n },\n {\n when: { variant: 'outline', tone: 'warning' },\n root: {\n color: '_warningText',\n borderColor: '_warning',\n backgroundColor: { default: '_surface', ':active:not([data-disabled])': '_warningSoft' },\n },\n },\n {\n when: { variant: 'outline', tone: 'info' },\n root: {\n color: '_infoText',\n borderColor: '_info',\n backgroundColor: { default: '_surface', ':active:not([data-disabled])': '_infoSoft' },\n },\n },\n // ───── 无底无边 ─────\n {\n when: { variant: 'ghost', tone: 'accent' },\n root: {\n color: '_accentText',\n backgroundColor: { default: 'transparent', ':active:not([data-disabled])': '_accentSoft' },\n },\n },\n {\n when: { variant: 'ghost', tone: 'neutral' },\n root: {\n color: '_text',\n backgroundColor: { default: 'transparent', ':active:not([data-disabled])': '_fill' },\n },\n },\n {\n when: { variant: 'ghost', tone: 'danger' },\n root: {\n color: '_dangerText',\n backgroundColor: { default: 'transparent', ':active:not([data-disabled])': '_dangerSoft' },\n },\n },\n {\n when: { variant: 'ghost', tone: 'success' },\n root: {\n color: '_successText',\n backgroundColor: { default: 'transparent', ':active:not([data-disabled])': '_successSoft' },\n },\n },\n {\n when: { variant: 'ghost', tone: 'warning' },\n root: {\n color: '_warningText',\n backgroundColor: { default: 'transparent', ':active:not([data-disabled])': '_warningSoft' },\n },\n },\n {\n when: { variant: 'ghost', tone: 'info' },\n root: {\n color: '_infoText',\n backgroundColor: { default: 'transparent', ':active:not([data-disabled])': '_infoSoft' },\n },\n },\n ],\n defaults: { variant: 'solid', tone: 'accent', size: 'md', radius: 'md' },\n});\n\n/**\n * 开放能力的属性:小程序运行时按 open-type 换成原生 button,平台事件统一成 opentype(mini 的 page.ts)。\n * 这些不是 button 的 DOM 属性,H5 里不写\n */\nfunction openTypeAttrs(\n openType: ButtonOpenType | undefined,\n scope: ButtonProps['authScope'],\n onOpenType: ButtonProps['onOpenType'],\n): Record<string, unknown> {\n if (!openType) return {};\n return {\n 'open-type': openType,\n scope,\n onOpentype: onOpenType && ((event: { detail?: unknown }) => onOpenType(event.detail)),\n };\n}\n\n/** 按钮(button > span 内容) */\nexport const Button = _component(\n ({\n variant,\n tone,\n size,\n radius,\n fullWidth = false,\n disabled = false,\n loading = false,\n loadingText,\n icon,\n iconEnd,\n children,\n propsContent,\n propsIcon,\n propsIconEnd,\n propsLoading,\n onClick,\n openType,\n authScope,\n onOpenType,\n type = 'button',\n ...rest\n }: ButtonProps) => {\n // 开放能力只在小程序里有\n const mini = isMiniProgram(useHost());\n return () => {\n const s = buttonRecipe({ variant, tone, size, radius, fullWidth });\n const busy = disabled || loading;\n return (\n <button\n {...mergeAttrs<'button'>({ sx: s.root }, rest, {\n type,\n disabled,\n // 加载中不响应点击(使用方的回调也不调用),但不设 disabled:保持外观与焦点\n onClick: busy ? undefined : onClick,\n 'aria-busy': loading || undefined,\n ...(mini ? openTypeAttrs(openType, authScope, onOpenType) : {}),\n ...dataAttrs({\n 'data-disabled': disabled || undefined,\n 'data-loading': loading || undefined,\n }),\n })}\n >\n <span {...mergeAttrs<'span'>({ sx: s.content }, propsContent)}>\n {loading ? (\n <Loading {...mergePartProps<LoadingProps>({ sx: s.icon }, propsLoading)} />\n ) : (\n icon && <Icon {...mergePartProps<IconProps>({ icon, sx: s.icon }, propsIcon)} />\n )}\n {loading && hasContent(loadingText) ? loadingText : children}\n {iconEnd && !loading && (\n <Icon {...mergePartProps<IconProps>({ icon: iconEnd, sx: s.icon }, propsIconEnd)} />\n )}\n </span>\n </button>\n );\n };\n },\n);\n"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 图标:渲染 Lucide 格式的图标数据,按名导入可摇树:
3
+ *
4
+ * <Icon icon={Search} /> 装饰性:对读屏隐藏
5
+ * <Icon icon={Trash2} aria-label="删除" /> 有名称:role="img"
6
+ *
7
+ * 大小默认 1em、颜色跟随文字(currentColor)。小程序里由渲染运行时画成遮罩,大小、颜色同样跟随。
8
+ * 自定义图标按同一格式写 `{ size?, node: [[标签, 属性, 子节点?], …] }`。
9
+ */
10
+ import type { LucideIconData } from '@lucide/icons';
11
+ import { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';
12
+ import { type PartProps } from '../utils/props';
13
+ /** 图标数据:Lucide 的标准格式 */
14
+ export type IconData = LucideIconData;
15
+ /** 绘制与无障碍相关的属性由组件管理 */
16
+ export type IconManaged = 'viewBox' | 'fill' | 'stroke' | 'stroke-width' | 'stroke-linecap' | 'stroke-linejoin' | 'role' | 'aria-hidden';
17
+ /** 未声明的属性落到 svg 上;传了 aria-label 才对读屏可见 */
18
+ export interface IconProps extends PartProps<'svg', IconManaged> {
19
+ /** 图标数据:`import { Search } from '@lucide/icons'` */
20
+ icon: IconData;
21
+ /** 大小(px);不写时为 1em,跟随所在文字 */
22
+ size?: number;
23
+ /**
24
+ * 线宽(24 格画布上的单位)
25
+ * @default 2
26
+ */
27
+ strokeWidth?: number;
28
+ /** 实心:轮廓里也填上文字颜色(评分的星、选中的心) */
29
+ filled?: boolean;
30
+ }
31
+ /** Icon 的样式:icon */
32
+ export declare const iconStyles: Readonly<{
33
+ icon: css.CompiledStyles;
34
+ }>;
35
+ /** 图标(svg):不传 aria-label 时是装饰性的 */
36
+ export declare const Icon: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<IconProps>;
37
+ /** 图标的子元素拼成的 SVG 标记,按图标数据缓存,作为 svg 的 innerHTML(不为每条路径建虚拟节点) */
38
+ export declare function iconMarkup(icon: IconData): string;
@@ -0,0 +1,65 @@
1
+ import { mergeAttrs } from "../utils/props.js";
2
+ import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
3
+ import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
4
+ import { createVNode } from "vue";
5
+ //#region src/base/Icon.tsx
6
+ /** 图标数据:Lucide 的标准格式 */
7
+ /** 绘制与无障碍相关的属性由组件管理 */
8
+ /** 未声明的属性落到 svg 上;传了 aria-label 才对读屏可见 */
9
+ /** Icon 的样式:icon */
10
+ var iconStyles = css.create({ icon: {
11
+ width: "1em",
12
+ height: "1em",
13
+ flexShrink: 0,
14
+ verticalAlign: "-0.125em"
15
+ } });
16
+ /** 图标(svg):不传 aria-label 时是装饰性的 */
17
+ var Icon = /*#__PURE__*/ _component((_props, _ctx) => {
18
+ const icon = readonlyProp(_props, "icon");
19
+ const size = readonlyProp(_props, "size");
20
+ const strokeWidth = readonlyProp(_props, "strokeWidth", 2);
21
+ const filled = readonlyProp(_props, "filled", false);
22
+ const rest = restProps(_props, _ctx, true);
23
+ return () => {
24
+ const labelled = rest["aria-label"] != null;
25
+ return createVNode("svg", mergeJsxProps(mergeAttrs({ sx: iconStyles.icon }, rest, {
26
+ style: size.value == null ? void 0 : { fontSize: `${size.value}px` },
27
+ viewBox: viewBox(icon.value),
28
+ fill: filled.value ? "currentColor" : "none",
29
+ stroke: "currentColor",
30
+ "stroke-width": strokeWidth.value,
31
+ "stroke-linecap": "round",
32
+ "stroke-linejoin": "round",
33
+ role: labelled ? "img" : void 0,
34
+ "aria-hidden": labelled ? void 0 : "true"
35
+ }), { "innerHTML": iconMarkup(icon.value) }), null);
36
+ };
37
+ }, {
38
+ name: "Icon",
39
+ props: [
40
+ "icon",
41
+ "size",
42
+ "strokeWidth",
43
+ "filled"
44
+ ]
45
+ });
46
+ function viewBox(icon) {
47
+ return `0 0 ${icon.width ?? icon.size ?? 24} ${icon.height ?? icon.size ?? 24}`;
48
+ }
49
+ /** Lucide 图标数据里的一个节点:[标签, 属性, 子节点] */
50
+ var markupCache = /* @__PURE__ */ new WeakMap();
51
+ /** 图标的子元素拼成的 SVG 标记,按图标数据缓存,作为 svg 的 innerHTML(不为每条路径建虚拟节点) */
52
+ function iconMarkup(icon) {
53
+ let markup = markupCache.get(icon);
54
+ if (markup === void 0) markupCache.set(icon, markup = icon.node.map(nodeMarkup).join(""));
55
+ return markup;
56
+ }
57
+ function nodeMarkup([tag, attrs, children]) {
58
+ if (!/^[a-z][a-z0-9-]*$/i.test(tag)) throw new Error(`图标数据里的标签名不合法:${tag}`);
59
+ return `<${tag}${Object.entries(attrs).filter(([name]) => name !== "key").map(([name, value]) => ` ${name}="${escapeAttribute(String(value))}"`).join("")}>${children?.map(nodeMarkup).join("") ?? ""}</${tag}>`;
60
+ }
61
+ var escapeAttribute = (value) => value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;");
62
+ //#endregion
63
+ export { Icon, iconMarkup, iconStyles };
64
+
65
+ //# sourceMappingURL=Icon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Icon.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/base/Icon.tsx"],"sourcesContent":["/**\n * 图标:渲染 Lucide 格式的图标数据,按名导入可摇树:\n *\n * <Icon icon={Search} /> 装饰性:对读屏隐藏\n * <Icon icon={Trash2} aria-label=\"删除\" /> 有名称:role=\"img\"\n *\n * 大小默认 1em、颜色跟随文字(currentColor)。小程序里由渲染运行时画成遮罩,大小、颜色同样跟随。\n * 自定义图标按同一格式写 `{ size?, node: [[标签, 属性, 子节点?], …] }`。\n */\nimport type { LucideIconData } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { mergeAttrs, type PartProps } from '../utils/props';\n\n/** 图标数据:Lucide 的标准格式 */\nexport type IconData = LucideIconData;\n\n/** 绘制与无障碍相关的属性由组件管理 */\nexport type IconManaged =\n | 'viewBox'\n | 'fill'\n | 'stroke'\n | 'stroke-width'\n | 'stroke-linecap'\n | 'stroke-linejoin'\n | 'role'\n | 'aria-hidden';\n\n/** 未声明的属性落到 svg 上;传了 aria-label 才对读屏可见 */\nexport interface IconProps extends PartProps<'svg', IconManaged> {\n /** 图标数据:`import { Search } from '@lucide/icons'` */\n icon: IconData;\n /** 大小(px);不写时为 1em,跟随所在文字 */\n size?: number;\n /**\n * 线宽(24 格画布上的单位)\n * @default 2\n */\n strokeWidth?: number;\n /** 实心:轮廓里也填上文字颜色(评分的星、选中的心) */\n filled?: boolean;\n}\n\n/** Icon 的样式:icon */\nexport const iconStyles = css.create({\n icon: {\n width: '1em',\n height: '1em',\n flexShrink: 0,\n // 与文字基线对齐\n verticalAlign: '-0.125em',\n },\n});\n\n/** 图标(svg):不传 aria-label 时是装饰性的 */\nexport const Icon = _component(\n ({ icon, size, strokeWidth = 2, filled = false, ...rest }: IconProps) =>\n () => {\n const labelled = rest['aria-label'] != null;\n return (\n <svg\n {...mergeAttrs<'svg'>({ sx: iconStyles.icon }, rest, {\n style: size == null ? undefined : { fontSize: `${size}px` },\n viewBox: viewBox(icon),\n fill: filled ? 'currentColor' : 'none',\n stroke: 'currentColor',\n // 写成属性而不是样式:小程序把 svg 画成遮罩图片,读不到页面的样式\n 'stroke-width': strokeWidth,\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n role: labelled ? 'img' : undefined,\n 'aria-hidden': labelled ? undefined : 'true',\n })}\n innerHTML={iconMarkup(icon)}\n />\n );\n },\n);\n\nfunction viewBox(icon: IconData) {\n const width = icon.width ?? icon.size ?? 24;\n const height = icon.height ?? icon.size ?? 24;\n return `0 0 ${width} ${height}`;\n}\n\n/** Lucide 图标数据里的一个节点:[标签, 属性, 子节点] */\ntype IconNode = IconData['node'][number];\n\nconst markupCache = new WeakMap<IconData, string>();\n\n/** 图标的子元素拼成的 SVG 标记,按图标数据缓存,作为 svg 的 innerHTML(不为每条路径建虚拟节点) */\nexport function iconMarkup(icon: IconData): string {\n let markup = markupCache.get(icon);\n if (markup === undefined) markupCache.set(icon, (markup = icon.node.map(nodeMarkup).join('')));\n return markup;\n}\n\nfunction nodeMarkup([tag, attrs, children]: IconNode): string {\n if (!/^[a-z][a-z0-9-]*$/i.test(tag)) throw new Error(`图标数据里的标签名不合法:${tag}`);\n // 节点里的 key 是给虚拟节点用的,不是 SVG 属性\n const attributes = Object.entries(attrs)\n .filter(([name]) => name !== 'key')\n .map(([name, value]) => ` ${name}=\"${escapeAttribute(String(value))}\"`)\n .join('');\n return `<${tag}${attributes}>${children?.map(nodeMarkup).join('') ?? ''}</${tag}>`;\n}\n\nconst escapeAttribute = (value: string) =>\n value.replaceAll('&', '&amp;').replaceAll('\"', '&quot;').replaceAll('<', '&lt;');\n"],"mappings":";;;;;;;;;AA4CA,IAAa,aAAa,IAAI,OAAO,EACnC,MAAM;CACJ,OAAO;CACP,QAAQ;CACR,YAAY;CAEZ,eAAe;AACjB,EACF,CAAC;;AAGD,IAAa,OAAO,0BAClB,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,eAA6B,CAAC;CAAA,MAAA,SAAAA,aAAA,QAAA,UAAW,KAAK;CAAA,MAAK,OAAIC,UAAA,QAAA,MAAA,IAAA;CAAA,aAC/C;EACJ,MAAM,WAAW,KAAK,iBAAiB;EACvC,OAAAC,YAAA,OAAAC,cAEQ,WAAkB,EAAE,IAAI,WAAW,KAAK,GAAG,MAAM;GACnD,OAAO,KAAA,SAAQ,OAAO,KAAA,IAAY,EAAE,UAAU,GAAA,KAAA,MAAA,IAAY;GAC1D,SAAS,QAAO,KAAA,KAAK;GACrB,MAAM,OAAA,QAAS,iBAAiB;GAChC,QAAQ;GAER,gBAAc,YAAA;GACd,kBAAkB;GAClB,mBAAmB;GACnB,MAAM,WAAW,QAAQ,KAAA;GACzB,eAAe,WAAW,KAAA,IAAY;EACxC,CAAC,GAAC,EAAA,aACS,WAAU,KAAA,KAAK,EAAC,CAAA,GAAA,IAAA;CAGjC;AAAC,GAAA;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACL;AAEA,SAAS,QAAQ,MAAgB;CAG/B,OAAO,OAFO,KAAK,SAAS,KAAK,QAAQ,GAEtB,GADJ,KAAK,UAAU,KAAK,QAAQ;AAE7C;;AAKA,IAAM,8BAAc,IAAI,QAA0B;;AAGlD,SAAgB,WAAW,MAAwB;CACjD,IAAI,SAAS,YAAY,IAAI,IAAI;CACjC,IAAI,WAAW,KAAA,GAAW,YAAY,IAAI,MAAO,SAAS,KAAK,KAAK,IAAI,UAAU,CAAC,CAAC,KAAK,EAAE,CAAE;CAC7F,OAAO;AACT;AAEA,SAAS,WAAW,CAAC,KAAK,OAAO,WAA6B;CAC5D,IAAI,CAAC,qBAAqB,KAAK,GAAG,GAAG,MAAM,IAAI,MAAM,gBAAgB,KAAK;CAM1E,OAAO,IAAI,MAJQ,OAAO,QAAQ,KAAK,CAAC,CACrC,QAAQ,CAAC,UAAU,SAAS,KAAK,CAAC,CAClC,KAAK,CAAC,MAAM,WAAW,IAAI,KAAI,IAAK,gBAAgB,OAAO,KAAK,CAAC,EAAC,EAAG,CAAC,CACtE,KAAK,EACS,EAAU,GAAI,UAAU,IAAI,UAAU,CAAC,CAAC,KAAK,EAAE,KAAK,GAAE,IAAK,IAAG;AACjF;AAEA,IAAM,mBAAmB,UACvB,MAAM,WAAW,KAAK,OAAO,CAAC,CAAC,WAAW,MAAK,QAAQ,CAAC,CAAC,WAAW,KAAK,MAAM"}
@@ -0,0 +1 @@
1
+ export declare const PrivacyPrompt: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<{}>;
@@ -0,0 +1,73 @@
1
+ import { useConfig } from "../config/context.js";
2
+ import { useHost } from "../utils/host.js";
3
+ import { Button } from "../base/Button.js";
4
+ import { Popup } from "../overlay/Popup.js";
5
+ import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
6
+ import { _component, mergeJsxProps } from "@zerodep-dev/vue-tsx-pro-tsx";
7
+ import { createVNode, onBeforeUnmount, ref } from "vue";
8
+ //#region src/capability/PrivacyPrompt.tsx
9
+ /** 同意按钮的 id:微信回复同意时要带上它,确认是用户点了这个按钮 */
10
+ var AGREE_ID = "vtm-privacy-agree";
11
+ var styles = css.create({
12
+ body: {
13
+ display: "flex",
14
+ flexDirection: "column",
15
+ gap: "_md",
16
+ padding: "_lg"
17
+ },
18
+ title: {
19
+ fontSize: "_lg",
20
+ fontWeight: "_semibold",
21
+ textAlign: "center"
22
+ },
23
+ message: {
24
+ fontSize: "_sm",
25
+ lineHeight: "_relaxed",
26
+ color: "_textMuted"
27
+ },
28
+ link: { color: "_accentText" },
29
+ actions: {
30
+ display: "flex",
31
+ gap: "_md"
32
+ },
33
+ action: { flex: "1 1 0" }
34
+ });
35
+ var PrivacyPrompt = /*#__PURE__*/ _component(() => {
36
+ const _cache = [];
37
+ const host = useHost();
38
+ const config = useConfig();
39
+ let reply = ref(null);
40
+ onBeforeUnmount(host.onPrivacyRequest((next) => reply.value = next));
41
+ const answer = (agreed) => {
42
+ reply.value?.(agreed, agreed ? AGREE_ID : void 0);
43
+ reply.value = null;
44
+ };
45
+ return () => {
46
+ const text = config.lang.privacy;
47
+ return createVNode(Popup, {
48
+ "_open": reply.value !== null,
49
+ "position": "bottom",
50
+ "round": true,
51
+ "closeOnOverlay": false,
52
+ "onOpenChange": _cache[0] || (_cache[0] = (open) => !open && answer(false))
53
+ }, { default: () => [createVNode("div", css.attrs(styles.body), [
54
+ createVNode("div", css.attrs(styles.title), [text.title]),
55
+ createVNode("div", css.attrs(styles.message), [text.message, createVNode("span", mergeJsxProps(css.attrs(styles.link), { "onClick": _cache[1] || (_cache[1] = () => void host.openPrivacyContract()) }), [text.contract])]),
56
+ createVNode("div", css.attrs(styles.actions), [createVNode(Button, {
57
+ "variant": "soft",
58
+ "tone": "neutral",
59
+ "sx": styles.action,
60
+ "onClick": _cache[2] || (_cache[2] = () => answer(false))
61
+ }, { default: () => [text.disagree] }), createVNode(Button, {
62
+ "id": AGREE_ID,
63
+ "openType": "agreePrivacyAuthorization",
64
+ "sx": styles.action,
65
+ "onOpenType": _cache[3] || (_cache[3] = () => answer(true))
66
+ }, { default: () => [text.agree] })])
67
+ ])] });
68
+ };
69
+ }, { name: "PrivacyPrompt" });
70
+ //#endregion
71
+ export { PrivacyPrompt };
72
+
73
+ //# sourceMappingURL=PrivacyPrompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PrivacyPrompt.js","names":["_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/capability/PrivacyPrompt.tsx"],"sourcesContent":["/**\n * 隐私协议弹窗:微信要求用户先同意隐私保护指引,才放行位置、相册、剪贴板这类隐私接口。平台拦下接口时\n * (宿主的 onPrivacyRequest)从底部弹出,同意按钮是 open-type=\"agreePrivacyAuthorization\" 的原生按钮。\n * ConfigProvider 自动渲染一个(在命令式反馈的出口里);H5 与支付宝不会触发。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { onBeforeUnmount } from 'vue';\nimport { Button } from '../base/Button';\nimport { useConfig } from '../config/context';\nimport { Popup } from '../overlay/Popup';\nimport { useHost } from '../utils/host';\nimport type { PrivacyReply } from './types';\n\n/** 同意按钮的 id:微信回复同意时要带上它,确认是用户点了这个按钮 */\nconst AGREE_ID = 'vtm-privacy-agree';\n\nconst styles = css.create({\n body: { display: 'flex', flexDirection: 'column', gap: '_md', padding: '_lg' },\n title: { fontSize: '_lg', fontWeight: '_semibold', textAlign: 'center' },\n message: { fontSize: '_sm', lineHeight: '_relaxed', color: '_textMuted' },\n link: { color: '_accentText' },\n actions: { display: 'flex', gap: '_md' },\n action: { flex: '1 1 0' },\n});\n\nexport const PrivacyPrompt = _component(() => {\n const host = useHost();\n const config = useConfig();\n let reply = _state<PrivacyReply | null>(null);\n onBeforeUnmount(host.onPrivacyRequest((next) => (reply = next)));\n\n const answer = (agreed: boolean) => {\n reply?.(agreed, agreed ? AGREE_ID : undefined);\n reply = null;\n };\n\n return () => {\n const text = config.lang.privacy;\n return (\n <Popup\n _open={reply !== null}\n position=\"bottom\"\n round\n closeOnOverlay={false}\n onOpenChange={(open) => !open && answer(false)}\n >\n <div {...css.attrs(styles.body)}>\n <div {...css.attrs(styles.title)}>{text.title}</div>\n <div {...css.attrs(styles.message)}>\n {text.message}\n <span {...css.attrs(styles.link)} onClick={() => void host.openPrivacyContract()}>\n {text.contract}\n </span>\n </div>\n <div {...css.attrs(styles.actions)}>\n <Button variant=\"soft\" tone=\"neutral\" sx={styles.action} onClick={() => answer(false)}>\n {text.disagree}\n </Button>\n <Button\n id={AGREE_ID}\n openType=\"agreePrivacyAuthorization\"\n sx={styles.action}\n onOpenType={() => answer(true)}\n >\n {text.agree}\n </Button>\n </div>\n </div>\n </Popup>\n );\n };\n});\n"],"mappings":";;;;;;;;;AAeA,IAAM,WAAW;AAEjB,IAAM,SAAS,IAAI,OAAO;CACxB,MAAM;EAAE,SAAS;EAAQ,eAAe;EAAU,KAAK;EAAO,SAAS;CAAM;CAC7E,OAAO;EAAE,UAAU;EAAO,YAAY;EAAa,WAAW;CAAS;CACvE,SAAS;EAAE,UAAU;EAAO,YAAY;EAAY,OAAO;CAAa;CACxE,MAAM,EAAE,OAAO,cAAc;CAC7B,SAAS;EAAE,SAAS;EAAQ,KAAK;CAAM;CACvC,QAAQ,EAAE,MAAM,QAAQ;AAC1B,CAAC;AAED,IAAa,gBAAgB,+BAAiB;CAAA,MAAA,SAAA,CAAA;CAC5C,MAAM,OAAO,QAAQ;CACrB,MAAM,SAAS,UAAU;CACzB,IAAI,QAAKA,IAA+B,IAAI;CAC5C,gBAAgB,KAAK,kBAAkB,SAAU,MAAA,QAAQ,IAAK,CAAC;CAE/D,MAAM,UAAU,WAAoB;EAClC,MAAA,QAAQ,QAAQ,SAAS,WAAW,KAAA,CAAS;EAC7C,MAAA,QAAQ;CACV;CAEA,aAAa;EACX,MAAM,OAAO,OAAO,KAAK;EACzB,OAAAC,YAAA,OAAA;GAAA,SAEW,MAAA,UAAU;GAAI,YAAA;GAAA,SAAA;GAAA,kBAGL;GAAK,gBAAA,OAAA,OAAA,OAAA,MACN,SAAS,CAAC,QAAQ,OAAO,KAAK;EAAC,GAAA,EAAA,eAAA,CAAAA,YAAA,OAErC,IAAI,MAAM,OAAO,IAAI,GAAC;GAAAA,YAAA,OACpB,IAAI,MAAM,OAAO,KAAK,GAAC,CAAG,KAAK,KAAK,CAAA;GAAAA,YAAA,OACpC,IAAI,MAAM,OAAO,OAAO,GAAC,CAC/B,KAAK,SAAOA,YAAA,QAAAC,cACH,IAAI,MAAM,OAAO,IAAI,GAAC,EAAA,WAAA,OAAA,OAAA,OAAA,WAAiB,KAAK,KAAK,oBAAoB,GAAC,CAAA,GAAA,CAC7E,KAAK,QAAQ,CAAA,CAAA,CAAA;GAAAD,YAAA,OAGT,IAAI,MAAM,OAAO,OAAO,GAAC,CAAAA,YAAA,QAAA;IAAA,WAAA;IAAA,QAAA;IAAA,MACU,OAAO;IAAM,WAAA,OAAA,OAAA,OAAA,WAAiB,OAAO,KAAK;GAAC,GAAA,EAAA,eAAA,CAClF,KAAK,QAAQ,EAAA,CAAA,GAAAA,YAAA,QAAA;IAAA,MAGV;IAAQ,YAAA;IAAA,MAER,OAAO;IAAM,cAAA,OAAA,OAAA,OAAA,WACC,OAAO,IAAI;GAAC,GAAA,EAAA,eAAA,CAE7B,KAAK,KAAK,EAAA,CAAA,CAAA,CAAA;EAAA,CAAA,CAAA,EAAA,CAAA;CAMvB;AACF,GAAC,EAAA,MAAA,gBAAA,CAAA"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * 宿主能力里与用户授权有关的部分(设备权限、存储、登录、支付、订阅消息、隐私协议)的类型。
3
+ * 三端的差异见 MOBILE.md §12;接口挂在 Host 上(utils/host.ts),H5 的实现在 capability/web.ts,小程序的在 mini 包里
4
+ */
5
+ /**
6
+ * 设备权限:
7
+ * location 精确位置、fuzzyLocation 模糊位置(微信里两者在 app.json 的声明互斥,二选一)、camera 相机、microphone 麦克风、
8
+ * album 保存到相册、bluetooth 蓝牙
9
+ */
10
+ export type DevicePermission = 'location' | 'fuzzyLocation' | 'camera' | 'microphone' | 'album' | 'bluetooth';
11
+ /**
12
+ * 权限状态:granted 已同意;denied 拒绝过(不会再弹系统授权,只能去设置页打开);
13
+ * prompt 还没问过(用到时会弹);unsupported 这一端没有这项能力
14
+ */
15
+ export type DevicePermissionState = 'granted' | 'denied' | 'prompt' | 'unsupported';
16
+ /** 设备权限 */
17
+ export interface HostPermissions {
18
+ /** 查状态,不弹窗 */
19
+ status(name: DevicePermission): Promise<DevicePermissionState>;
20
+ /**
21
+ * 申请:没问过时弹系统授权,返回之后的状态;拒绝过的直接返回 denied(引导去设置页由 usePermission 做)。
22
+ * 支付宝没有提前申请的接口:位置、蓝牙调一次对应能力弹出授权;相机、相册、麦克风没有不打开界面的调用,
23
+ * 返回 prompt,表示照常去用、用到时平台自己问
24
+ */
25
+ request(name: DevicePermission): Promise<DevicePermissionState>;
26
+ /** 打开小程序的设置页,用户改完回来后返回该权限的新状态;H5 不能代开浏览器设置,返回当前状态 */
27
+ openSettings(name: DevicePermission): Promise<DevicePermissionState>;
28
+ }
29
+ /** 本地存储:值按 JSON 存取,同步读写(三端都有同步接口) */
30
+ export interface HostStorage {
31
+ /** 读;没有或解析不了时 undefined */
32
+ get<T = unknown>(key: string): T | undefined;
33
+ set(key: string, value: unknown): void;
34
+ remove(key: string): void;
35
+ /** 全部的键 */
36
+ keys(): string[];
37
+ }
38
+ /** 登录:拿平台的一次性凭证,交给后端换会话(组件库不碰密钥) */
39
+ export interface LoginOptions {
40
+ /**
41
+ * 支付宝的授权范围:base 静默(只拿用户标识),user 要用户同意(会员基础信息)。微信与 H5 忽略
42
+ * @default 'base'
43
+ */
44
+ scope?: 'base' | 'user';
45
+ }
46
+ /** 登录凭证:微信 wx.login 的 code,支付宝 my.getAuthCode 的 authCode,H5 是应用自己的实现给的 */
47
+ export interface LoginResult {
48
+ platform: 'web' | 'app' | 'wx' | 'my';
49
+ code: string;
50
+ }
51
+ /**
52
+ * 支付参数,按后端下单的结果填:
53
+ * - wechat:微信支付(小程序 wx.requestPayment;微信内的 H5 走 JSAPI,要加 appId)
54
+ * - alipay:支付宝小程序 my.tradePay 的交易号
55
+ * - redirect:H5 在微信、支付宝之外的浏览器里,跳到支付页(微信 H5 支付的 mweb_url、支付宝手机网站支付的地址)
56
+ */
57
+ export type PaymentOrder = {
58
+ provider: 'wechat';
59
+ timeStamp: string;
60
+ nonceStr: string;
61
+ /** prepay_id=… */
62
+ package: string;
63
+ signType: 'MD5' | 'HMAC-SHA256' | 'RSA';
64
+ paySign: string;
65
+ /** 微信内 H5(JSAPI)必填;小程序里不用 */
66
+ appId?: string;
67
+ } | {
68
+ provider: 'alipay';
69
+ tradeNO: string;
70
+ } | {
71
+ provider: 'redirect';
72
+ url: string;
73
+ };
74
+ /**
75
+ * 支付结果。success 也只是前端看到的结果,发货前要以后端查单为准;pending 是结果未知或处理中(支付宝 8000、6004,
76
+ * H5 跳走之后),一定要去后端查
77
+ */
78
+ export interface PaymentResult {
79
+ status: 'success' | 'cancel' | 'pending' | 'fail';
80
+ /** 平台的原始结果码或信息,排查用 */
81
+ raw?: unknown;
82
+ }
83
+ /** 订阅消息每个模板的结果:accept 同意、reject 拒绝、ban 被后台封禁、unsupported 这一端没有 */
84
+ export type SubscribeState = 'accept' | 'reject' | 'ban' | 'unsupported';
85
+ /** 手机号授权按钮的结果,原样交给后端:微信给 code(后端换号码),支付宝给加密的 response(后端解密) */
86
+ export interface PhoneNumberResult {
87
+ platform: 'wx' | 'my';
88
+ code?: string;
89
+ response?: string;
90
+ }
91
+ /** 隐私协议的回复:同意时带上点的按钮的 id(微信要用它确认是用户点了同意按钮) */
92
+ export type PrivacyReply = (agreed: boolean, buttonId?: string) => void;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 设备权限的组合函数:查状态、申请;拒绝过的弹对话框引导去设置页(小程序里打开设置页,H5 只能说明)。
3
+ *
4
+ * const location = usePermission('location');
5
+ * const locate = async () => {
6
+ * if (await location.request()) host.… // 已同意
7
+ * };
8
+ * // location.status.value:granted / denied / prompt / unsupported,查到之前是 null
9
+ */
10
+ import { type Ref } from 'vue';
11
+ import type { DevicePermission, DevicePermissionState } from './types';
12
+ export interface PermissionHandle {
13
+ /** 当前状态;还没查到时 null */
14
+ readonly status: Readonly<Ref<DevicePermissionState | null>>;
15
+ /**
16
+ * 申请:可以接着用了返回 true——已同意,或平台在用到时自己问(申请后仍是 prompt,如支付宝的相机)。
17
+ * 拒绝过的先引导去设置页,从设置页回来再看一次
18
+ */
19
+ request(): Promise<boolean>;
20
+ /** 重新查状态(比如页面重新显示时) */
21
+ refresh(): Promise<DevicePermissionState>;
22
+ }
23
+ export declare function usePermission(name: DevicePermission): PermissionHandle;
@@ -0,0 +1,52 @@
1
+ import { useConfig } from "../config/context.js";
2
+ import { isMiniProgram, useHost } from "../utils/host.js";
3
+ import { showConfirmDialog, showDialog } from "../feedback/Dialog.js";
4
+ import { shallowRef } from "vue";
5
+ //#region src/capability/usePermission.ts
6
+ /**
7
+ * 设备权限的组合函数:查状态、申请;拒绝过的弹对话框引导去设置页(小程序里打开设置页,H5 只能说明)。
8
+ *
9
+ * const location = usePermission('location');
10
+ * const locate = async () => {
11
+ * if (await location.request()) host.… // 已同意
12
+ * };
13
+ * // location.status.value:granted / denied / prompt / unsupported,查到之前是 null
14
+ */
15
+ function usePermission(name) {
16
+ const host = useHost();
17
+ const config = useConfig();
18
+ const status = shallowRef(null);
19
+ const refresh = async () => status.value = await host.permissions.status(name);
20
+ refresh();
21
+ /** 拒绝过:小程序里问要不要去设置页;H5 不能代开浏览器设置、原生应用的设置页要用户自己去,只说明 */
22
+ const guide = async () => {
23
+ const { title, message, openSettings } = config.lang.permissionDenied;
24
+ const text = message(config.lang.permissions[name]);
25
+ if (!isMiniProgram(host)) {
26
+ await showDialog({
27
+ title,
28
+ message: text
29
+ });
30
+ return "denied";
31
+ }
32
+ return await showConfirmDialog({
33
+ title,
34
+ message: text,
35
+ confirmText: openSettings
36
+ }) ? host.permissions.openSettings(name) : "denied";
37
+ };
38
+ return {
39
+ status,
40
+ refresh,
41
+ async request() {
42
+ let next = await host.permissions.request(name);
43
+ if (next === "denied") next = await guide();
44
+ status.value = next;
45
+ return next === "granted" || next === "prompt";
46
+ }
47
+ };
48
+ }
49
+ //#endregion
50
+ export { usePermission };
51
+
52
+ //# sourceMappingURL=usePermission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePermission.js","names":[],"sources":["../../src/capability/usePermission.ts"],"sourcesContent":["/**\n * 设备权限的组合函数:查状态、申请;拒绝过的弹对话框引导去设置页(小程序里打开设置页,H5 只能说明)。\n *\n * const location = usePermission('location');\n * const locate = async () => {\n * if (await location.request()) host.… // 已同意\n * };\n * // location.status.value:granted / denied / prompt / unsupported,查到之前是 null\n */\nimport { shallowRef, type Ref } from 'vue';\nimport { useConfig } from '../config/context';\nimport { showConfirmDialog, showDialog } from '../feedback/Dialog';\nimport { isMiniProgram, useHost } from '../utils/host';\nimport type { DevicePermission, DevicePermissionState } from './types';\n\nexport interface PermissionHandle {\n /** 当前状态;还没查到时 null */\n readonly status: Readonly<Ref<DevicePermissionState | null>>;\n /**\n * 申请:可以接着用了返回 true——已同意,或平台在用到时自己问(申请后仍是 prompt,如支付宝的相机)。\n * 拒绝过的先引导去设置页,从设置页回来再看一次\n */\n request(): Promise<boolean>;\n /** 重新查状态(比如页面重新显示时) */\n refresh(): Promise<DevicePermissionState>;\n}\n\nexport function usePermission(name: DevicePermission): PermissionHandle {\n const host = useHost();\n const config = useConfig();\n const status = shallowRef<DevicePermissionState | null>(null);\n const refresh = async () => (status.value = await host.permissions.status(name));\n void refresh();\n\n /** 拒绝过:小程序里问要不要去设置页;H5 不能代开浏览器设置、原生应用的设置页要用户自己去,只说明 */\n const guide = async (): Promise<DevicePermissionState> => {\n const { title, message, openSettings } = config.lang.permissionDenied;\n const text = message(config.lang.permissions[name]);\n if (!isMiniProgram(host)) {\n await showDialog({ title, message: text });\n return 'denied';\n }\n const go = await showConfirmDialog({ title, message: text, confirmText: openSettings });\n return go ? host.permissions.openSettings(name) : 'denied';\n };\n\n return {\n status,\n refresh,\n async request() {\n let next = await host.permissions.request(name);\n if (next === 'denied') next = await guide();\n status.value = next;\n return next === 'granted' || next === 'prompt';\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AA2BA,SAAgB,cAAc,MAA0C;CACtE,MAAM,OAAO,QAAQ;CACrB,MAAM,SAAS,UAAU;CACzB,MAAM,SAAS,WAAyC,IAAI;CAC5D,MAAM,UAAU,YAAa,OAAO,QAAQ,MAAM,KAAK,YAAY,OAAO,IAAI;CAC9E,QAAa;;CAGb,MAAM,QAAQ,YAA4C;EACxD,MAAM,EAAE,OAAO,SAAS,iBAAiB,OAAO,KAAK;EACrD,MAAM,OAAO,QAAQ,OAAO,KAAK,YAAY,KAAK;EAClD,IAAI,CAAC,cAAc,IAAI,GAAG;GACxB,MAAM,WAAW;IAAE;IAAO,SAAS;GAAK,CAAC;GACzC,OAAO;EACT;EAEA,OAAO,MADU,kBAAkB;GAAE;GAAO,SAAS;GAAM,aAAa;EAAa,CAAC,IAC1E,KAAK,YAAY,aAAa,IAAI,IAAI;CACpD;CAEA,OAAO;EACL;EACA;EACA,MAAM,UAAU;GACd,IAAI,OAAO,MAAM,KAAK,YAAY,QAAQ,IAAI;GAC9C,IAAI,SAAS,UAAU,OAAO,MAAM,MAAM;GAC1C,OAAO,QAAQ;GACf,OAAO,SAAS,aAAa,SAAS;EACxC;CACF;AACF"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 存在本地的响应式值:读的是存储里的值(没有时用默认值),改了自动写回。三端都是同步存储。
3
+ *
4
+ * const history = useStorage<string[]>('search-history', []);
5
+ * history.value = [keyword, ...history.value].slice(0, 10);
6
+ */
7
+ import { type Ref } from 'vue';
8
+ export declare function useStorage<T>(key: string, initial: T): Ref<T>;
@@ -0,0 +1,19 @@
1
+ import { useHost } from "../utils/host.js";
2
+ import { shallowRef, watch } from "vue";
3
+ //#region src/capability/useStorage.ts
4
+ /**
5
+ * 存在本地的响应式值:读的是存储里的值(没有时用默认值),改了自动写回。三端都是同步存储。
6
+ *
7
+ * const history = useStorage<string[]>('search-history', []);
8
+ * history.value = [keyword, ...history.value].slice(0, 10);
9
+ */
10
+ function useStorage(key, initial) {
11
+ const { storage } = useHost();
12
+ const value = shallowRef(storage.get(key) ?? initial);
13
+ watch(value, (next) => storage.set(key, next));
14
+ return value;
15
+ }
16
+ //#endregion
17
+ export { useStorage };
18
+
19
+ //# sourceMappingURL=useStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStorage.js","names":[],"sources":["../../src/capability/useStorage.ts"],"sourcesContent":["/**\n * 存在本地的响应式值:读的是存储里的值(没有时用默认值),改了自动写回。三端都是同步存储。\n *\n * const history = useStorage<string[]>('search-history', []);\n * history.value = [keyword, ...history.value].slice(0, 10);\n */\nimport { shallowRef, watch, type Ref } from 'vue';\nimport { useHost } from '../utils/host';\n\nexport function useStorage<T>(key: string, initial: T): Ref<T> {\n const { storage } = useHost();\n const value = shallowRef(storage.get<T>(key) ?? initial) as Ref<T>;\n // shallowRef:整个换掉才写回(数组、对象改里面的项不会触发,照不可变的写法赋新值)\n watch(value, (next) => storage.set(key, next));\n return value;\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,WAAc,KAAa,SAAoB;CAC7D,MAAM,EAAE,YAAY,QAAQ;CAC5B,MAAM,QAAQ,WAAW,QAAQ,IAAO,GAAG,KAAK,OAAO;CAEvD,MAAM,QAAQ,SAAS,QAAQ,IAAI,KAAK,IAAI,CAAC;CAC7C,OAAO;AACT"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 授权类能力的浏览器实现(宿主约定见 utils/host.ts,类型见 types.ts)。
3
+ * 浏览器没有平台登录,也只在微信内置浏览器里能调起微信支付:这两项由应用经 ConfigProvider 的 host 补上自己的实现
4
+ */
5
+ import type { HostPermissions, HostStorage, LoginResult, PaymentOrder, PaymentResult, PhoneNumberResult, PrivacyReply, SubscribeState } from './types';
6
+ export declare const webPermissions: HostPermissions;
7
+ /** localStorage 在隐私模式、禁用存储时会抛错:读不到当作没有,写不进去就算了 */
8
+ export declare const webStorage: HostStorage;
9
+ export declare const webCapabilities: {
10
+ permissions: HostPermissions;
11
+ storage: HostStorage;
12
+ login(): Promise<LoginResult>;
13
+ pay(order: PaymentOrder): Promise<PaymentResult>;
14
+ subscribe(templateIds: string[]): Promise<Record<string, SubscribeState>>;
15
+ phoneNumber(): Promise<PhoneNumberResult>;
16
+ onPrivacyRequest: (_listener: (reply: PrivacyReply) => void) => () => void;
17
+ openPrivacyContract: () => Promise<void>;
18
+ };
@@ -0,0 +1,109 @@
1
+ //#region src/capability/web.ts
2
+ /** 设备权限 → Permissions API 的名字;album 在 H5 是下载文件、蓝牙要用户在设备选择框里选,不在这里 */
3
+ var QUERY_NAMES = {
4
+ location: "geolocation",
5
+ fuzzyLocation: "geolocation",
6
+ camera: "camera",
7
+ microphone: "microphone"
8
+ };
9
+ async function status(name) {
10
+ if (name === "album") return "granted";
11
+ if (name === "bluetooth") return "bluetooth" in navigator ? "prompt" : "unsupported";
12
+ try {
13
+ return (await navigator.permissions.query({ name: QUERY_NAMES[name] })).state;
14
+ } catch {
15
+ return "prompt";
16
+ }
17
+ }
18
+ /** 调一次对应的能力,让浏览器弹出授权 */
19
+ async function ask(name) {
20
+ if (name === "location" || name === "fuzzyLocation") try {
21
+ await new Promise((resolve, reject) => navigator.geolocation.getCurrentPosition(resolve, reject, { maximumAge: Infinity }));
22
+ return "granted";
23
+ } catch (error) {
24
+ return error.code === 1 ? "denied" : status(name);
25
+ }
26
+ if (name === "camera" || name === "microphone") try {
27
+ const stream = await navigator.mediaDevices.getUserMedia(name === "camera" ? { video: true } : { audio: true });
28
+ for (const track of stream.getTracks()) track.stop();
29
+ return "granted";
30
+ } catch (error) {
31
+ return error.name === "NotAllowedError" ? "denied" : status(name);
32
+ }
33
+ return status(name);
34
+ }
35
+ var webPermissions = {
36
+ status,
37
+ async request(name) {
38
+ const current = await status(name);
39
+ return current === "prompt" ? ask(name) : current;
40
+ },
41
+ openSettings: status
42
+ };
43
+ /** localStorage 在隐私模式、禁用存储时会抛错:读不到当作没有,写不进去就算了 */
44
+ var webStorage = {
45
+ get(key) {
46
+ try {
47
+ const text = localStorage.getItem(key);
48
+ return text === null ? void 0 : JSON.parse(text);
49
+ } catch {
50
+ return;
51
+ }
52
+ },
53
+ set(key, value) {
54
+ try {
55
+ localStorage.setItem(key, JSON.stringify(value));
56
+ } catch {}
57
+ },
58
+ remove(key) {
59
+ try {
60
+ localStorage.removeItem(key);
61
+ } catch {}
62
+ },
63
+ keys() {
64
+ try {
65
+ return Object.keys(localStorage);
66
+ } catch {
67
+ return [];
68
+ }
69
+ }
70
+ };
71
+ /** 微信内置浏览器的 JSAPI 支付:结果按 err_msg 的后缀判断(ok、cancel,其余是失败) */
72
+ function payInWeChat(order) {
73
+ const bridge = globalThis.WeixinJSBridge;
74
+ if (!bridge) throw new Error("微信支付在 H5 里只能在微信内置浏览器中调起;其它浏览器请用 redirect(H5 支付)");
75
+ if (!order.appId) throw new Error("微信内 H5 支付(JSAPI)要带 appId");
76
+ const { provider: _, ...params } = order;
77
+ return new Promise((resolve) => bridge.invoke("getBrandWCPayRequest", params, (result) => {
78
+ const outcome = result.err_msg.split(":").at(-1);
79
+ resolve({
80
+ status: outcome === "ok" ? "success" : outcome === "cancel" ? "cancel" : "fail",
81
+ raw: result
82
+ });
83
+ }));
84
+ }
85
+ var webCapabilities = {
86
+ permissions: webPermissions,
87
+ storage: webStorage,
88
+ login() {
89
+ return Promise.reject(/* @__PURE__ */ new Error("H5 没有平台登录:在 ConfigProvider 的 host 里给出 login 的实现(跳转 OAuth 等)"));
90
+ },
91
+ async pay(order) {
92
+ if (order.provider === "wechat") return payInWeChat(order);
93
+ if (order.provider === "alipay") throw new Error("tradeNO 只能在支付宝小程序里调起;H5 用 redirect(手机网站支付的地址)");
94
+ location.href = order.url;
95
+ return { status: "pending" };
96
+ },
97
+ async subscribe(templateIds) {
98
+ return Object.fromEntries(templateIds.map((id) => [id, "unsupported"]));
99
+ },
100
+ phoneNumber() {
101
+ return Promise.reject(/* @__PURE__ */ new Error("H5 没有平台的手机号授权,用短信验证码"));
102
+ },
103
+ onPrivacyRequest: (_listener) => () => {},
104
+ openPrivacyContract: () => Promise.resolve()
105
+ };
106
+ //#endregion
107
+ export { webCapabilities, webPermissions, webStorage };
108
+
109
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","names":[],"sources":["../../src/capability/web.ts"],"sourcesContent":["/**\n * 授权类能力的浏览器实现(宿主约定见 utils/host.ts,类型见 types.ts)。\n * 浏览器没有平台登录,也只在微信内置浏览器里能调起微信支付:这两项由应用经 ConfigProvider 的 host 补上自己的实现\n */\nimport type {\n DevicePermission,\n DevicePermissionState,\n HostPermissions,\n HostStorage,\n LoginResult,\n PaymentOrder,\n PaymentResult,\n PhoneNumberResult,\n PrivacyReply,\n SubscribeState,\n} from './types';\n\n/** 设备权限 → Permissions API 的名字;album 在 H5 是下载文件、蓝牙要用户在设备选择框里选,不在这里 */\nconst QUERY_NAMES: Partial<Record<DevicePermission, string>> = {\n location: 'geolocation',\n fuzzyLocation: 'geolocation',\n camera: 'camera',\n microphone: 'microphone',\n};\n\nasync function status(name: DevicePermission): Promise<DevicePermissionState> {\n if (name === 'album') return 'granted';\n if (name === 'bluetooth') return 'bluetooth' in navigator ? 'prompt' : 'unsupported';\n // Safari 16 之前没有 Permissions API;查不了的名字(旧版 Firefox 的 camera)会抛错:都当作还没问过\n try {\n const result = await navigator.permissions.query({ name: QUERY_NAMES[name] } as never);\n return result.state;\n } catch {\n return 'prompt';\n }\n}\n\n/** 调一次对应的能力,让浏览器弹出授权 */\nasync function ask(name: DevicePermission): Promise<DevicePermissionState> {\n if (name === 'location' || name === 'fuzzyLocation') {\n try {\n await new Promise<GeolocationPosition>((resolve, reject) =>\n navigator.geolocation.getCurrentPosition(resolve, reject, { maximumAge: Infinity }),\n );\n return 'granted';\n } catch (error) {\n return (error as GeolocationPositionError).code === 1 ? 'denied' : status(name);\n }\n }\n if (name === 'camera' || name === 'microphone') {\n try {\n const stream = await navigator.mediaDevices.getUserMedia(\n name === 'camera' ? { video: true } : { audio: true },\n );\n for (const track of stream.getTracks()) track.stop();\n return 'granted';\n } catch (error) {\n return (error as DOMException).name === 'NotAllowedError' ? 'denied' : status(name);\n }\n }\n return status(name);\n}\n\nexport const webPermissions: HostPermissions = {\n status,\n async request(name) {\n const current = await status(name);\n return current === 'prompt' ? ask(name) : current;\n },\n openSettings: status,\n};\n\n/** localStorage 在隐私模式、禁用存储时会抛错:读不到当作没有,写不进去就算了 */\nexport const webStorage: HostStorage = {\n get<T>(key: string) {\n try {\n const text = localStorage.getItem(key);\n return text === null ? undefined : (JSON.parse(text) as T);\n } catch {\n return undefined;\n }\n },\n set(key, value) {\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n // 存储满了或被禁用\n }\n },\n remove(key) {\n try {\n localStorage.removeItem(key);\n } catch {\n // 同上\n }\n },\n keys() {\n try {\n return Object.keys(localStorage);\n } catch {\n return [];\n }\n },\n};\n\n/** 微信内置浏览器注入的桥 */\ninterface WeixinJSBridge {\n invoke(\n name: 'getBrandWCPayRequest',\n params: Record<string, string>,\n callback: (result: { err_msg: string }) => void,\n ): void;\n}\n\n/** 微信内置浏览器的 JSAPI 支付:结果按 err_msg 的后缀判断(ok、cancel,其余是失败) */\nfunction payInWeChat(order: Extract<PaymentOrder, { provider: 'wechat' }>) {\n const bridge = (globalThis as { WeixinJSBridge?: WeixinJSBridge }).WeixinJSBridge;\n if (!bridge)\n throw new Error(\n '微信支付在 H5 里只能在微信内置浏览器中调起;其它浏览器请用 redirect(H5 支付)',\n );\n if (!order.appId) throw new Error('微信内 H5 支付(JSAPI)要带 appId');\n const { provider: _, ...params } = order;\n return new Promise<PaymentResult>((resolve) =>\n bridge.invoke('getBrandWCPayRequest', params as Record<string, string>, (result) => {\n const outcome = result.err_msg.split(':').at(-1);\n resolve({\n status: outcome === 'ok' ? 'success' : outcome === 'cancel' ? 'cancel' : 'fail',\n raw: result,\n });\n }),\n );\n}\n\nexport const webCapabilities = {\n permissions: webPermissions,\n storage: webStorage,\n login(): Promise<LoginResult> {\n return Promise.reject(\n new Error('H5 没有平台登录:在 ConfigProvider 的 host 里给出 login 的实现(跳转 OAuth 等)'),\n );\n },\n async pay(order: PaymentOrder): Promise<PaymentResult> {\n if (order.provider === 'wechat') return payInWeChat(order);\n if (order.provider === 'alipay')\n throw new Error('tradeNO 只能在支付宝小程序里调起;H5 用 redirect(手机网站支付的地址)');\n // 跳走之后页面就没了;从支付页回来时由应用去后端查单\n location.href = order.url;\n return { status: 'pending' };\n },\n async subscribe(templateIds: string[]): Promise<Record<string, SubscribeState>> {\n return Object.fromEntries(templateIds.map((id) => [id, 'unsupported' as const]));\n },\n phoneNumber(): Promise<PhoneNumberResult> {\n return Promise.reject(new Error('H5 没有平台的手机号授权,用短信验证码'));\n },\n // 隐私协议只有微信小程序有\n onPrivacyRequest: (_listener: (reply: PrivacyReply) => void) => () => {},\n openPrivacyContract: () => Promise.resolve(),\n};\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { type Host } from '../utils/host';
2
+ /**
3
+ * 原生应用的宿主能力:在浏览器的实现上换掉设备权限、存储、震动、剪贴板、选图;
4
+ * 顺带接上安卓的返回键(页面能后退就后退,到头了退出应用),状态栏的文字颜色、iOS 键盘的深浅色跟着页面主题。只在原生外壳里调用。
5
+ *
6
+ * 键盘弹起时的布局由外壳负责:安卓上 Capacitor 8 的 SystemBars 把键盘高度垫在 WebView 下面,
7
+ * iOS 上 Keyboard 插件的 resize: native 把 WebView 缩到键盘上面——两端页面都变矮,底部固定的元素与正在输入的框露在键盘上方
8
+ */
9
+ export declare function createCapacitorHost(): Promise<Host>;