apptify 0.2.0 → 0.5.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 (72) hide show
  1. package/README.md +95 -22
  2. package/dist/apptify.css +1 -1
  3. package/dist/apptify.js +4153 -2177
  4. package/dist/components/button.d.ts +1 -1
  5. package/dist/components/content.d.ts +73 -362
  6. package/dist/components/date-picker.d.ts +10 -0
  7. package/dist/components/forms.d.ts +77 -18
  8. package/dist/components/foundation.d.ts +2061 -111
  9. package/dist/components/gallery-scrollbar.d.ts +46 -0
  10. package/dist/components/mobile-image-viewer.d.ts +166 -0
  11. package/dist/components/navibar.d.ts +80 -0
  12. package/dist/components/overlays.d.ts +127 -158
  13. package/dist/components/tabs.d.ts +117 -0
  14. package/dist/core/context.d.ts +573 -9
  15. package/dist/core/desktop-image-enter.d.ts +6 -0
  16. package/dist/core/desktop-image-return.d.ts +1 -0
  17. package/dist/core/device.d.ts +5 -0
  18. package/dist/core/image-gallery-motion.d.ts +2 -0
  19. package/dist/core/image-geometry.d.ts +28 -0
  20. package/dist/core/image-motion.d.ts +18 -0
  21. package/dist/core/layers.d.ts +2 -0
  22. package/dist/core/motion.d.ts +2 -0
  23. package/dist/core/popup-motion.d.ts +12 -0
  24. package/dist/core/popup-placement.d.ts +31 -0
  25. package/dist/core/preview-navigation.d.ts +5 -0
  26. package/dist/index.d.ts +1214 -339
  27. package/docs/COMPONENTS.md +17 -15
  28. package/docs/PLAYGROUND-TEMPLATE.md +74 -0
  29. package/docs/VALIDATION.md +24 -0
  30. package/package.json +26 -7
  31. package/templates/playground/README.md +17 -0
  32. package/templates/playground/gitignore +3 -0
  33. package/templates/playground/index.html +26 -0
  34. package/templates/playground/package.json +26 -0
  35. package/templates/playground/playground/App.vue +73 -0
  36. package/templates/playground/playground/ComponentDemo.vue +234 -0
  37. package/templates/playground/playground/catalog.ts +80 -0
  38. package/templates/playground/playground/components/ComponentIndex.vue +62 -0
  39. package/templates/playground/playground/components/PageFooter.vue +3 -0
  40. package/templates/playground/playground/components/UserMenu.vue +132 -0
  41. package/templates/playground/playground/components/not-found-paper-scene.vue +210 -0
  42. package/templates/playground/playground/components/not-found-paper-story.ts +149 -0
  43. package/templates/playground/playground/env.d.ts +1 -0
  44. package/templates/playground/playground/main.ts +8 -0
  45. package/templates/playground/playground/router/index.ts +24 -0
  46. package/templates/playground/playground/style.css +228 -0
  47. package/templates/playground/playground/views/[...all].vue +68 -0
  48. package/templates/playground/playground/views/components.vue +126 -0
  49. package/templates/playground/playground/views/index.vue +75 -0
  50. package/templates/playground/playground/views/settings.vue +98 -0
  51. package/templates/playground/public/images/SOURCES.md +21 -0
  52. package/templates/playground/public/images/airpods-max-orange.jpg +0 -0
  53. package/templates/playground/public/images/architecture.jpg +0 -0
  54. package/templates/playground/public/images/city.jpg +0 -0
  55. package/templates/playground/public/images/coast.jpg +0 -0
  56. package/templates/playground/public/images/desert.jpg +0 -0
  57. package/templates/playground/public/images/food.jpg +0 -0
  58. package/templates/playground/public/images/forest.jpg +0 -0
  59. package/templates/playground/public/images/lake.jpg +0 -0
  60. package/templates/playground/public/images/mountains.jpg +0 -0
  61. package/templates/playground/public/images/snow.jpg +0 -0
  62. package/templates/playground/public/images/sunset.jpg +0 -0
  63. package/templates/playground/public/images/waterfall.jpg +0 -0
  64. package/templates/playground/source-snapshot.json +111 -0
  65. package/templates/playground/tsconfig.json +24 -0
  66. package/templates/playground/vite.config.ts +9 -0
  67. package/tooling/create-playground.mjs +35 -0
  68. package/tooling/prepare-package.mjs +35 -0
  69. package/tooling/prepare-playground.mjs +90 -0
  70. package/tooling/test-playground-package.mjs +188 -0
  71. package/tooling/vite.d.ts +5 -0
  72. package/tooling/vite.js +10 -0
@@ -1,6 +1,6 @@
1
1
  # 组件 API 参考
2
2
 
3
- 本页按 `src/index.ts` 合并的组件注册表列出当前 **68 个导出组件**,不是未来功能清单。组件名均可换成 kebab-case 模板标签,例如 `AppleOtpInput` 对应 `<apple-otp-input>`。
3
+ 本页按 `src/index.ts` 合并的组件注册表列出当前 **66 个导出组件**,不是未来功能清单。组件名均可换成 kebab-case 模板标签,例如 `AppleOtpInput` 对应 `<apple-otp-input>`。
4
4
 
5
5
  ## 公共约定
6
6
 
@@ -34,7 +34,7 @@ Tabs、TabBar、SegmentedControl、DatePicker 日期/时间页签均不使用 Ri
34
34
  | `AppleButton` | M;`variant=primary`(primary/secondary/outline/ghost/danger)、`size=medium`(small/medium/large)、`icon: Component`、`iconOnly`、`label`、`disabled`、`loading`、`ripple=true`、`href`、`type=button` | `click(event)`;default slot。纯图标按钮须提供 label。只有允许完整动效时启用 Ripple。 |
35
35
  | `AppleLink` | `href`、`external`、`disabled` | default slot;external 使用新窗口并设置 `noopener noreferrer`。 |
36
36
  | `AppleCard` | M;`title`、`subtitle`、`text`、`eyebrow`、`icon: Component 或图片 URL`、`iconColor`、`image`、`imageAlt`、`href`、`zoom=small`(big/small/none)、`shadow=normal`(normal/static/focused/none) | default、media、icon、title、actions slots;href 为标题链接,不会把所有嵌套控件变成一个大按钮。 |
37
- | `AppleImage` | M;必填 `src`、`alt`;`preview=true`、`gallery: string[]`、`index=0`、`squared`、`aspectRatio='4/3'`、`fit=cover`(cover/contain) | caption slot;默认点击预览;图片错误状态;squared 覆盖 aspectRatio。 |
37
+ | `AppleImage` | M;`gallery: AppleImageItem | (string/AppleImageItem)[]`(唯一来源,默认空数组,单图可传对象);`preview=true`、`carousel=false`、`disabled=false`、`label`、`galleryLayout=compact`(compact/tiled/tiled-wrap,所有设备)、`galleryShape=natural`(natural/square)、`index=0`、`squared`、`aspectRatio='4/3'`、`fit=cover`(cover/contain) | caption 与 item({item,index,active}) slots;carousel 强制 compact、常显控件且保留自由滚动;默认点击预览;统一入口,按触屏能力选择预览交互;完整渲染图片组,compact 在固定框内横滑,桌面触控板支持自由连续横滑、一次手势可跨越多张,快速点击翻页延续当前位置和速度;hover 不缩放,多图显示左右翻页按钮(单图小窗与全屏均无翻页箭头),跨多张点击小点时直接衔接当前画面与目标图并支持连续改选,tiled 等高横向平铺,溢出时底部常显自定义滚动条,支持拖动、点击轨道与键盘,滑块比例随内容和容器尺寸同步,tiled-wrap 等宽保留图片比例,按列从上往下紧密堆叠,避免按行对齐产生空白;平铺 hover 只缩放当前图片;galleryShape=square 强制 1:1 并居中裁剪(覆盖 fit),squared 同样作用于图片组;`update:index`、`change(index)`;图片错误状态;squared 覆盖 aspectRatio。 |
38
38
  | `AppleSearch` | `v-model: string`、`placeholder='搜索'`、`label='搜索'`、`disabled` | `search(value)` 在 Enter 时发出;清空更新 model。 |
39
39
  | `AppleContainer` | `width=1200`(number/string) | default slot;数字宽度按 px,宽度不会超过可用空间。 |
40
40
  | `AppleStack` | `direction=column`、`gap=16`、`align=stretch`、`wrap=true` | default slot;Flex 布局;数字 gap 按 px。 |
@@ -77,13 +77,16 @@ validator 返回 `true` 才通过;字符串作为错误信息,`false` 显示
77
77
 
78
78
  日期格式示例:`YYYY/MM`、`YYYY/MM/DD`、`YYYY/MM/DD HH:mm`、`YYYY/MM/DD HH:mm:ss`、`HH:mm`、`HH:mm:ss`。显示格式与模型格式分离:模型依次返回 `YYYY-MM`、`YYYY-MM-DD`、`YYYY-MM-DDTHH:mm[:ss]` 或 `HH:mm[:ss]`。独立 `AppleTimePicker` 已移除,纯时间使用 `<apple-date-picker format="HH:mm" />`;不支持原生 ISO week 模式及日期范围选择。
79
79
 
80
- ## 内容与移动交互 · 29
80
+ ## 内容与移动交互 · 30
81
81
 
82
82
  以下组件均接受 M。
83
83
 
84
+ 导航组件默认协调同一文档中的图片预览;对话框内预览只协调该对话框内的导航。独立页面区域可用 `data-apple-navigation-scope` 容器隔离;多个预览都开始退出后恢复导航。关闭、取消和卸载均释放状态。
85
+
84
86
  | 组件 | 主要 Props | Events / Slots / 说明 |
85
87
  | --- | --- | --- |
86
- | `AppleNavibar` | `v-model: string/number`、`items: AppleItem[]`、`brand`、`brandHref='/'`、`label='主导航'`、`fixed=true`、`breakpoint=640` | `change(value,item)`、`toggle(open)`;brand、item `{item,active}`、actions `{close}` slots。固定模式自带占位;栏宽不超过 breakpoint 时折叠为三横杠菜单;选择、Escape、外部点击或焦点移出后收起。`fixed=false` 可嵌入容器。 |
88
+ | `AppleNavibar` | `v-model: string/number`、`items: AppleItem[]`、`brand`、`brandHref='/'`、`label='主导航'`、`fixed=true`、`breakpoint=640`、`hideOnPreview`(默认跟随 fixed) | `change(value,item)`、`toggle(open)`;brand、item `{item,active}`、actions `{close}` slots。固定模式自带占位;栏宽不超过 breakpoint 时折叠为三横杠菜单;选择、Escape、外部点击或焦点移出后收起。`fixed=false` 可嵌入容器。参与预览时保留占位并隐藏,退出开始时与黑色背景淡出同步淡入。 |
89
+ | `AppleAside` | M;`hideOnPreview=true` | 原生 aside 容器,布局由调用方提供;图片预览期间保留尺寸、隐藏并禁用交互,退出开始时 140ms 淡入。正常文档流侧栏可设 `hideOnPreview=false`。 |
87
90
  | `AppleTabs` | `v-model: string/number`、`items`、`label`、`disabled` | `change(value)`;`panel-${value}` slot `{item}` 或 default `{item,value}`;可非受控;方向键/Home/End。 |
88
91
  | `AppleTabBar` | 与 AppleTabs 相同 | 顶角圆角页签,移动选中背景与内容滑动。 |
89
92
  | `AppleBreadcrumbs` | `items`、`label='当前位置'` | `click(item,event)`;非最后项使用 item.href 或按钮;最后项为当前页文本;无下划线。 |
@@ -104,10 +107,8 @@ validator 返回 `true` 才通过;字符串作为错误信息,`false` 显示
104
107
  | `AppleDivider` | `label`、`vertical` | default slot;水平或垂直分隔语义。 |
105
108
  | `AppleSteps` | `v-model: number=0`、`items`、`clickable`、`disabled`、`label` | `change(index)`;模型是从 0 开始的步骤位置,不是 item.value。 |
106
109
  | `AppleTimeline` | `items: AppleTimelineItem[]`、`orientation=vertical`(horizontal/vertical)、`label` | item slot `{item}`;选项增加 `time?` 和 `tone?: default/success/danger`。 |
107
- | `AppleCarousel` | `items: AppleSlide[]`、`v-model: number`、`label`、`disabled` | `change(index)`;item slot `{item,index,active}`;选项增加 src/alt;滚动吸附与按钮/键盘,不自动播放。 |
108
110
  | `ApplePullRefresh` | `v-model: boolean`、`disabled`、`threshold=72`、`label` | `refresh(done)`;default slot `{refresh,refreshing}`;在滚动顶部下拉,也提供刷新按钮;完成必须调用 done 或将 model 设为 false。 |
109
111
  | `AppleInfiniteScroll` | `loading`、`error: boolean/string`、`finished`、`disabled`、`distance=120`、`finishedText` | `load(done)`、`retry(done)`;default slot;IntersectionObserver 及手动加载按钮;完成后调用 done,或令 loading 从 true 回到 false。 |
110
- | `AppleSwipeCell` | `v-model: boolean`、`disabled`、`label` | default、actions slot `{close}`;横向手势或操作按钮展开,Escape 关闭。 |
111
112
  | `AppleBackTop` | `target: CSS selector=''`、`threshold=300`、`label`、`disabled` | `click(event)`;default slot;空 target 使用 window,达到滚动阈值才显示。 |
112
113
  | `AppleFloatingGroup` | `backTop=true`、`threshold=300`、`target`、`label` | default slot 放附加操作,回顶按钮始终排最下;默认固定右下并考虑安全区。 |
113
114
  | `AppleMarquee` | `text`、`duration=24`、`paused`、`label` | default slot;duration 单位为秒;带暂停按钮;减少动态效果时停止连续滚动。 |
@@ -133,7 +134,7 @@ Table 另支持 `virtual=false`、`height=360`、`rowHeight=48`、`overscan=5`
133
134
  </apple-table>
134
135
  ```
135
136
 
136
- ## 弹层组件 · 10
137
+ ## 弹层组件 · 9
137
138
 
138
139
  以下组件除 Host 外均接受 M。
139
140
 
@@ -143,12 +144,11 @@ Table 另支持 `virtual=false`、`height=360`、`rowHeight=48`、`overscan=5`
143
144
  | `AppleDrawer` | 同 Modal;`placement=right`(left/right);`width=440` | 默认无 footer;全高侧边抽屉。 |
144
145
  | `AppleSheet` | 同 Modal;`width=440` | 默认无 footer;底部面板,移动端全宽;桌面样式最小设计宽度 540px,仍受可用宽度约束。 |
145
146
  | `AppleSnackbar` | `v-model: boolean=true`、`message`、`title`、`tone=default`(default/info/success/warning/danger/error)、`duration=4000`、`action`、`closable=true` | `close(value,reason)`、`action()`;default slot;duration 单位毫秒,0 常驻;直接使用时位置由业务布局决定。 |
146
- | `AppleOverlayHost` | 无公开配置 | 自动渲染当前上下文 overlays.entries;Provider 已包含,不要重复挂载。 |
147
+ | `AppleOverlayHost` | 无公开配置 | 自动渲染当前上下文 overlays.entries.value;Provider 已包含,不要重复挂载。 |
147
148
  | `ApplePopover` | `v-model?: boolean`、`label='更多'`、`disabled`、`placement=bottom`(top/bottom/left/right)、`align=start`(start/center/end)、`width=280`、`role=dialog`、`openOnHover`、`trapFocus=true`、`panelClass` | `open()`、`close()`;activator `{props,open,close,isOpen}`、default `{close}`;支持非受控模式与屏幕边缘避让。 |
148
149
  | `AppleTooltip` | 必填 `text`;`placement=top`、`disabled` | default slot 为触发元素;悬停/焦点显示,Escape 关闭;短文本提示。 |
149
150
  | `AppleMenu` | `v-model?: boolean`、`label='操作'`、`items: AppleMenuItem[]`、`selected`、`disabled` | `select(value,item)`;activator slot 同 Popover、item slot `{item}`;选择后关闭,方向键/Home/End。 |
150
- | `AppleActionSheet` | `v-model: boolean`、`title`、`message`、`items: AppleMenuItem[]`、`cancelText='取消'` | `select(value,item)`、`close(value,reason)`;选择返回条目 value。 |
151
- | `AppleImageViewer` | `v-model: boolean`、`images: (string/AppleViewerImage)[]`、`v-model:index=0`、`loop` | `change(index)`、`close()`、`error(event)`;缩放与手势来自 @panzoom/panzoom,滚轮按实际滚动量连续缩放并保持鼠标焦点。 |
151
+ | `AppleActionSheet` | `v-model: boolean`、`title`、`message`、`items: AppleMenuItem[]`、`cancelText='取消'` | `select(value,item)`、`close(value,reason)`;默认插槽支持自定义模板,提供 `select(item)`、`close(value)`,覆盖 items 内容;取消按钮保留。无拖拽指示条。 |
152
152
 
153
153
  Modal 公共属性:`title`、`message`、`ariaLabel='对话框'`、`persistent`、`loading`、`closeOnConfirm=true`、`confirmText='确定'`、`cancelText='取消'`、`showFooter`、`closable=true`、`width`、`tone=default`。Dialog 默认 width 为 480px。空按钮文本会隐藏对应按钮。
154
154
 
@@ -176,7 +176,7 @@ Modal slots:`title`、default `{close}`、footer `{close,confirm,cancel}`。
176
176
 
177
177
  `AppleMenuItem = { label; value: string|number; disabled?; danger?; description?; icon?: Component }`。
178
178
 
179
- `AppleViewerImage = { src: string; alt?: string; title?: string }`。数组字符串是 URL;需要可访问描述或标题时传对象。index 从 0 开始。
179
+ `AppleViewerImage = { src: string; alt?: string; title?: string; width?: number; height?: number }` 仍是 `AppleImage` 可用的公共图片类型。图片数组字符串是 URL;需要可访问描述或标题时传对象。`AppleImageItem` 扩展该类型,`src` 可选以支持 carousel 的自定义内容,另有 `label`、`value`、`description`。顶层不接受 src/alt;index 从 0 开始、默认 0,支持 `v-model:index`,非有限值归零、越界值截取到有效范围。普通图片忽略无效空项;空列表显示占位且不打开预览。点击放大由 `AppleImage` 内部管理,不另行注册独立查看器。
180
180
 
181
181
  ## 上下文与服务
182
182
 
@@ -184,10 +184,12 @@ Modal slots:`title`、default `{close}`、footer `{close,confirm,cancel}`。
184
184
 
185
185
  | API | 作用 |
186
186
  | --- | --- |
187
- | `theme.set(name)` | 切换注册主题或 system。未知名称抛出错误。 |
188
- | `theme.register(name,tokens,scheme='light')` | 注册自定义主题并补齐基础 token。 |
189
- | `theme.name / resolved / current / themes` | 当前配置名、解析后名称、主题内容、主题注册表。 |
190
- | `motion.set(mode)` | 即时修改全局动效策略。 |
187
+ | `theme.value.set(name)` | 切换注册主题或 system。未知名称抛出错误。 |
188
+ | `theme.value.register(name,tokens,scheme='light')` | 注册自定义主题并补齐基础 token。 |
189
+ | `theme.value.name / resolved / current / themes` | 当前配置名、解析后名称、主题内容、主题注册表。 |
190
+ | `overlays.entries.value` | 当前弹层列表,`Ref<OverlayEntry[]>`。 |
191
+ | `portalTarget.value` | 弹层挂载 DOM 元素。 |
192
+ | `motion.value.set(mode)` | 即时修改全局动效策略。 |
191
193
  | `onMessage(channel,listener)` | 注册监听器,返回取消订阅函数。 |
192
194
  | `sendMessage(channel,payload?)` | 返回该 channel 全部监听器的返回值数组。 |
193
195
  | `dialog(options)` | 打开 dialog 并返回独立句柄。 |
@@ -0,0 +1,74 @@
1
+ # 完整 playground 应用模板
2
+
3
+ apptify 包既提供组件库,也携带完整的可编辑应用模板。模板源码在包内 templates/playground/,包括导航与页面布局、账号菜单与登录/注册表单、主题/动效/玻璃设置、404 纸片故事、全部示例页面及图片资源。
4
+
5
+ 这些应用文件不增加组件库的公开 exports。复制出的项目通过 apptify 和 apptify/vite 使用已打包组件库,不能导入原仓库的 src。
6
+
7
+ ## 从本地包创建应用
8
+
9
+ 当前尚未发布 npm。使用 Node.js 22 或 24 LTS,先在库仓库生成包:
10
+
11
+ ~~~sh
12
+ npm ci
13
+ npm pack --pack-destination /tmp
14
+ ~~~
15
+
16
+ npm pack 会先执行构建与模板准备。将输出的实际 tgz 路径代入以下命令;在一个用于安装组件库的空目录执行:
17
+
18
+ ~~~sh
19
+ npm init -y
20
+ npm install /tmp/apptify-0.4.0.tgz
21
+ npx --no-install apptify-playground ../my-app
22
+ cd ../my-app
23
+ npm install
24
+ npm run typecheck
25
+ npm run build
26
+ npm run dev
27
+ ~~~
28
+
29
+ 复制命令要求目标目录不存在或为空,拒绝覆盖已有项目。复制过程不会自动安装项目依赖。
30
+
31
+ my-app/vendor/ 会携带组件库 tgz,应用依赖使用相对 file: 依赖。可以把整个 my-app 移动到另一目录或发给其他使用者;后续安装无需原仓库或用于调用命令的安装目录。运行与构建所需依赖全部声明在应用的 package.json。更新库时可把新 tgz 放入 vendor/ 并调整该相对依赖。
32
+
33
+ 也可手工复制包内 templates/playground/,把 gitignore 重命名为 .gitignore,再将模板 package.json 中的 apptify 版本依赖改为本地 tgz;复制命令会自动完成这些步骤。仓库内 templates/playground 是打包时生成的目录,开发者应编辑 playground 源文件;使用者复制出的项目可以直接编辑。
34
+
35
+ ## 开始业务开发
36
+
37
+ 可以只删除以下两页,然后编写自己的页面:
38
+
39
+ ~~~text
40
+ playground/views/index.vue
41
+ playground/views/components.vue
42
+ ~~~
43
+
44
+ 文件路由从 playground/views/ 自动生成,不存在对这两页的硬导入。删除后重启开发服务即可;模板导航自动隐藏不存在的页面,品牌链接回到第一个保留的导航项,404 的“返回首页”在没有首页时回到设置。
45
+
46
+ 新页面例如 playground/views/orders.vue 会得到 /orders 路由。将入口加入 playground/App.vue 的 navigation 数组即可出现在顶部导航。自己的 index.vue 仍对应首页。生产服务器需把 history 路由请求回退到 index.html。
47
+
48
+ 删除组件总览后,ComponentDemo.vue、catalog.ts 和 components/ComponentIndex.vue 可以留着,也可按需删除;设置、账号、404 与布局不依赖它们。不需要清理库组件依赖或维护手写路由列表。
49
+
50
+ | 可编辑位置 | 用途 |
51
+ | --- | --- |
52
+ | playground/App.vue | 公共导航、品牌、账号入口和页面进入动效 |
53
+ | playground/router/index.ts | 滚动恢复、hash 定位和页面标题 |
54
+ | playground/components/UserMenu.vue | 账号菜单、登录/注册、邮箱验证码和忘记密码入口 |
55
+ | playground/views/settings.vue | 外观、动效和玻璃偏好 |
56
+ | playground/views/[...all].vue 与 components/not-found-paper-* | 404 页面与故事 |
57
+ | playground/style.css | 应用布局与页面样式 |
58
+ | public/images/ | 示例图片与来源说明 |
59
+
60
+ 账户操作仍是 UI 演示,需要接入使用者自己的后端。图片来源说明随模板保留在 public/images/SOURCES.md。
61
+
62
+ ## 本地交付验证
63
+
64
+ 在库仓库执行:
65
+
66
+ ~~~sh
67
+ npm run test:package
68
+ ~~~
69
+
70
+ 此检查会构建原库消费者、生成真实 npm tarball、检查每份 playground 源码和静态资源都在包内,并在临时空目录安装包后调用复制命令。随后删除最初的安装目录,独立安装、类型检查、构建和启动复制项目,实测导航、图片、设置、登录及 404;再删除首页、组件总览及其可选演示文件,重复类型检查、构建和浏览器验证。原库消费测试仍单独执行。
71
+
72
+ source-snapshot.json 记录本次复制前各源码与资源的 SHA-256。构建前后与验收末尾都会比对库、模板、资源及配置;若用户在打包中途编辑,检查要求重新取最新快照,不会回写或恢复旧源码。命令打印临时目录与验收 JSON 路径,失败时也保留该目录用于检查。
73
+
74
+ 已生成的具体 tgz 也可直接复验:运行 node tooling/test-playground-package.mjs /absolute/path/apptify-0.4.0.tgz。验收结果记录该文件 SHA-256,避免并行构建替换了被验证对象。
@@ -34,3 +34,27 @@
34
34
  - 可变行高、服务端数据协议和所有大规模数据场景;Table 当前的虚拟滚动为固定行高,已验证 5000 行示例及原地追加数据。
35
35
 
36
36
  测试执行环境为 macOS、Node.js 23.11.0。构建与测试实际通过,但 Vitest 将奇数版本 Node 标记为非支持版本;日常开发和 CI 建议使用 Node.js 22 或 24 LTS。
37
+
38
+ ## 2026-10-01 手机图片预览
39
+
40
+ - `npm test`:9 个文件、123 项单元测试通过。
41
+ - 本轮相关浏览器回归:`gallery.spec.ts`、`overlays-redesign.spec.ts`、`mobile-images.spec.ts` 共 57 项通过,包含 Chrome 实际双指触摸输入和 WebKit 手机模拟;另增 1 项可控时钟检查通过,精确验证 199ms 第二击可放大、200ms 单击退出。
42
+ - 新增检查覆盖双指同时缩放与平移、松手边界约束、上划不退出、下滑背景渐隐、单点退出、动画中途关闭、缩略图黑色占位切换、compact 两端定位、tiled/contain 比例、画布外图片回位、320px、横竖屏、减少动态效果与卸载清理。
43
+ - 切图额外验证慢拖、快速轻扫、快速拖动的松手速度连续性;动画中途接住、取消下滑、从当前位置关闭;2/5 张循环首尾衔接。双端按钮检查模糊、200% 饱和度、半透明背景和按下态;手机控件淡入期间保留图片的 backdrop 采样范围。
44
+ - 双击额外验证浏览器鼠标与实际触摸输入、点击焦点缩放和恢复、减少/关闭动效、缩放中途接住、第二次触摸长按或拖动、双指取消单击,以及叠层弹窗和立即关闭重开时的等待清理。回位动画在延迟关闭开始时捕获实际帧、黑色占位和滚动锁。
45
+ - `npm run build` 与独立消费示例构建通过,包含 TypeScript 检查、ESM 包和类型声明生成。
46
+ - 手机交互采用独立渲染分支;桌面图片缩放、旋转、方向按钮与键盘回归通过。未进行实体 iPhone 验证。
47
+
48
+ ## 2026-10-02 AppleImage 统一设备判断
49
+
50
+ - AppleImage 使用统一入口,缩略图和预览共用触屏检测:`navigator.maxTouchPoints > 0` 或 `(any-pointer: coarse)`,不再根据窗口宽度选择交互。布局选项在所有设备上可用。
51
+ - `foundation.test.ts`、`overlays.test.ts`、`image-device.test.ts`:40 项单元测试通过,包含混合输入设备、粗指针回退、无 matchMedia 和服务端环境。
52
+ - 图片设备、手势和覆盖层回归 46 项通过;各尺寸总览及 WebKit 手机冒烟另 5 项通过。设备组合覆盖 390px/1440px、触屏/非触屏,并检查打开期间调整窗口及关闭重开。
53
+ - `npm run build` 通过,包含类型检查、演示构建、组件库构建和类型声明。
54
+
55
+ ## 2026-10-02 共享状态统一使用 ref
56
+
57
+ - 主题、主题注册表、动效、系统深色状态、弹层列表、挂载点和 Provider 上下文统一使用 `ref`;组件、演示与文档同步使用 `.value`。
58
+ - 全量既有 127 项单元测试通过,另增 1 项共享 ref 身份与解构后响应更新检查通过。
59
+ - 主题切换、系统动效、通知、嵌套弹窗及主题菜单共 5 项浏览器回归通过。
60
+ - 生产构建及类型声明生成通过。
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "apptify",
3
- "version": "0.2.0",
4
- "description": "An Apple-inspired, responsive Vue 3 component library with Vuetify ripple.",
3
+ "version": "0.5.0",
4
+ "description": "An Apple-inspired, responsive Vue 3 component library.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "files": [
8
8
  "dist",
9
- "docs"
9
+ "docs",
10
+ "tooling",
11
+ "templates/playground"
10
12
  ],
11
13
  "main": "./dist/apptify.js",
12
14
  "module": "./dist/apptify.js",
@@ -16,21 +18,28 @@
16
18
  "types": "./dist/index.d.ts",
17
19
  "import": "./dist/apptify.js"
18
20
  },
19
- "./style.css": "./dist/apptify.css"
21
+ "./style.css": "./dist/apptify.css",
22
+ "./vite": {
23
+ "types": "./tooling/vite.d.ts",
24
+ "import": "./tooling/vite.js"
25
+ }
20
26
  },
21
27
  "sideEffects": [
22
28
  "**/*.css"
23
29
  ],
24
30
  "scripts": {
25
- "dev": "vite --host 127.0.0.1",
31
+ "dev": "vite",
26
32
  "build": "vue-tsc --noEmit && vite build && vite build --config vite.lib.config.ts && vue-tsc -p tsconfig.lib.json",
27
33
  "typecheck": "vue-tsc --noEmit",
28
34
  "test": "vitest run",
29
35
  "test:e2e": "playwright test",
30
- "test:package": "npm run build && vite build --config vite.consumer.config.ts",
31
- "preview": "vite preview --host 127.0.0.1"
36
+ "test:package": "npm run prepack && vite build --config vite.consumer.config.ts && node tooling/test-playground-package.mjs",
37
+ "preview": "vite preview --host 127.0.0.1",
38
+ "prepare:playground": "node tooling/prepare-playground.mjs",
39
+ "prepack": "node tooling/prepare-package.mjs"
32
40
  },
33
41
  "peerDependencies": {
42
+ "vite-plugin-pages": "^0.32.4",
34
43
  "vue": "^3.5.0",
35
44
  "vuetify": "^3.9.0"
36
45
  },
@@ -47,9 +56,19 @@
47
56
  "jsdom": "^26.0.0",
48
57
  "typescript": "^5.8.2",
49
58
  "vite": "^6.3.4",
59
+ "vite-plugin-pages": "^0.32.5",
50
60
  "vitest": "^4.1.11",
51
61
  "vue": "^3.5.13",
62
+ "vue-router": "^4.6.4",
52
63
  "vue-tsc": "^2.2.8",
53
64
  "vuetify": "^3.9.0"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "vite-plugin-pages": {
68
+ "optional": true
69
+ }
70
+ },
71
+ "bin": {
72
+ "apptify-playground": "./tooling/create-playground.mjs"
54
73
  }
55
74
  }
@@ -0,0 +1,17 @@
1
+ # Apptify 可编辑应用模板
2
+
3
+ 完整源码位于 playground/,静态图片与来源说明位于 public/images/。导航、账号/登录、设置、404 和页面动效均可直接编辑复用。
4
+
5
+ 通过 apptify-playground 复制命令创建时,vendor/ 包含本地组件库 tgz,package.json 使用相对 file: 依赖;移动整个项目后仍可独立安装。
6
+
7
+ 运行 npm install、npm run typecheck、npm run build;开发使用 npm run dev。请使用 Node.js 22 或 24 LTS。
8
+
9
+ 开始业务开发可直接删除 playground/views/index.vue 和 playground/views/components.vue,再添加自己的页面;文件路由自动生成,导航自动隐藏已删除页面,品牌链接回到第一个保留的导航页面。删除页面后重启开发服务。
10
+
11
+ 导航项在 playground/App.vue 的 navigation 数组;页面文件在 playground/views/;布局和账号入口在 App.vue 与 playground/components/UserMenu.vue。
12
+
13
+ 设置保留主题、动效与玻璃偏好。404 在 playground/views/[...all].vue;首页和组件总览没有被其他页面硬导入。ComponentDemo.vue、catalog.ts 和 ComponentIndex.vue 可以保留,也可在删除总览页后按需删除。
14
+
15
+ 登录、注册、忘记密码、邮箱验证码仍为 UI 演示入口,需要接入自己的后端。部署采用 history 路由,服务器应把未知页面路径回退到 index.html。
16
+
17
+ 若手工复制包内 templates/playground,先将 package.json 的 apptify 依赖替换为实际本地 tgz 路径,或安装自己的正式发布版本;当前项目尚未发布 npm。
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ dist/
3
+ .env.local
@@ -0,0 +1,26 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="theme-color" content="#f5f5f7" />
7
+ <style>html, body { background-color: var(--app-page-bg, #f5f5f7); }</style>
8
+ <script>
9
+ // Apply the saved page canvas before the application bundle can paint.
10
+ try {
11
+ const preference = JSON.parse(localStorage.getItem('apptify:preferences') || '{}');
12
+ const dark = preference.theme === 'dark' || (preference.theme === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
13
+ const background = preference.theme === 'graphite' ? '#f3f4f4' : preference.theme === 'rose' ? '#faf7f8' : dark ? '#161617' : '#f5f5f7';
14
+ document.documentElement.style.setProperty('--app-page-bg', background);
15
+ document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
16
+ document.querySelector('meta[name="theme-color"]').setAttribute('content', background);
17
+ } catch { /* Storage can be unavailable; the default canvas remains valid. */ }
18
+ </script>
19
+ <meta name="description" content="Apptify: Apple-inspired Vue 3 components. Built for every screen." />
20
+ <title>Apptify · 组件实验室</title>
21
+ </head>
22
+ <body>
23
+ <div id="app"></div>
24
+ <script type="module" src="/playground/main.ts"></script>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "apptify-app",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "typecheck": "vue-tsc --noEmit",
9
+ "build": "vue-tsc --noEmit && vite build",
10
+ "preview": "vite preview --host 127.0.0.1"
11
+ },
12
+ "dependencies": {
13
+ "apptify": "^0.5.0",
14
+ "vue": "^3.5.0",
15
+ "vue-router": "^4.6.4",
16
+ "vuetify": "^3.9.0",
17
+ "lucide-vue-next": "^0.468.0"
18
+ },
19
+ "devDependencies": {
20
+ "@vitejs/plugin-vue": "^5.2.3",
21
+ "typescript": "^5.8.2",
22
+ "vite": "^6.3.4",
23
+ "vite-plugin-pages": "^0.32.5",
24
+ "vue-tsc": "^2.2.8"
25
+ }
26
+ }
@@ -0,0 +1,73 @@
1
+ <template>
2
+ <apple-provider>
3
+ <apple-navibar class="site-nav" :model-value="$route.path" :items="navigation" @click.capture="navigate">
4
+ <template #brand>
5
+ <router-link class="brand" :to="homePath" aria-label="Apptify 首页"><span class="brand-symbol">a</span><span>Apptify</span></router-link>
6
+ </template>
7
+ <template #actions><UserMenu /></template>
8
+ </apple-navibar>
9
+ <router-view v-slot="{ Component, route }">
10
+ <div ref="routePage" class="route-page">
11
+ <component :is="Component" :key="route.path" />
12
+ </div>
13
+ </router-view>
14
+ </apple-provider>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
19
+ import { useRoute, useRouter } from 'vue-router'
20
+ import { useApple } from 'apptify'
21
+ import { animateEntrance } from 'apptify'
22
+ import UserMenu from './components/UserMenu.vue'
23
+
24
+ const router = useRouter()
25
+ const route = useRoute()
26
+ const apple = useApple()
27
+ watch(() => [apple.theme.value.current.tokens.bg, apple.theme.value.current.scheme], ([background, scheme]) => {
28
+ document.documentElement.style.setProperty('--app-page-bg', background)
29
+ document.documentElement.style.colorScheme = scheme
30
+ document.querySelector('meta[name="theme-color"]')?.setAttribute('content', background)
31
+ }, { immediate: true, flush: 'sync' })
32
+ const routePage = ref<HTMLElement>()
33
+ let entrances: Animation[] = []
34
+ let entranceFrame = 0
35
+
36
+ function cancelEntrance() {
37
+ cancelAnimationFrame(entranceFrame)
38
+ entrances.forEach(animation => animation.cancel())
39
+ entrances = []
40
+ }
41
+ function queueEntrance() {
42
+ cancelEntrance()
43
+ // Let the router restore the scroll position before animating the page.
44
+ // Animate the fixed sidebar itself so it shares the entrance without changing its containing block.
45
+ entranceFrame = requestAnimationFrame(() => {
46
+ const targets = [routePage.value?.querySelector<HTMLElement>('main'), routePage.value?.querySelector<HTMLElement>('.sidebar')]
47
+ targets.forEach((element, index) => {
48
+ if (!element) return
49
+ const animation = animateEntrance(element, index === 1 ? 'x' : 'y')
50
+ if (animation) entrances.push(animation)
51
+ })
52
+ })
53
+ }
54
+ watch(() => route.path, queueEntrance, { flush: 'post' })
55
+ watch(() => [apple.motion.value.mode, apple.motion.value.reduced], () => {
56
+ if (apple.motion.value.reduced || ['none', 'reduced'].includes(apple.motion.value.mode)) cancelEntrance()
57
+ })
58
+ onMounted(queueEntrance)
59
+ onBeforeUnmount(cancelEntrance)
60
+ const navigation = [
61
+ { label: '首页', value: '/', href: '/' },
62
+ { label: '组件', value: '/components', href: '/components' },
63
+ { label: '设置', value: '/settings', href: '/settings' },
64
+ ].filter(item => router.getRoutes().some(page => page.path === item.href))
65
+ const homePath = navigation[0]?.href ?? '/'
66
+
67
+ function navigate(event: MouseEvent) {
68
+ const link = (event.target as HTMLElement).closest<HTMLAnchorElement>('a.apple-navibar__item')
69
+ if (!link || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return
70
+ event.preventDefault()
71
+ void router.push(link.getAttribute('href')!)
72
+ }
73
+ </script>