apptify 0.5.4 → 0.6.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.
package/README.md CHANGED
@@ -1,59 +1,23 @@
1
1
  # Apptify
2
2
 
3
- 面向桌面与移动端的 Vue 3 组件库,采用 Apple 风格设计,统一使用 `<apple-*>` 标签。支持主题切换、减少动效、表单、图片预览与多层弹窗;支持 JavaScript、TypeScript 和 Options API。
3
+ 面向桌面与移动端的 Vue 3 组件库,基于 Vuetify Material UI 融合 Apple 风格设计,统一使用 `<apple-*>` 标签。
4
4
 
5
5
  [npm](https://www.npmjs.com/package/apptify) · [组件 API](docs/COMPONENTS.md) · [应用模板](docs/PLAYGROUND-TEMPLATE.md)
6
6
 
7
- ## 安装
7
+ ## 快速开始
8
8
 
9
9
  ```sh
10
- npm install apptify vue@^3.5 vuetify@^3.9
10
+ npm create apptify
11
11
  ```
12
12
 
13
- Vue 与 Vuetify 是 peer dependencies。Vuetify 仅用于 Ripple,无需安装其插件、导入完整样式或使用 `<v-app>`。
14
-
15
- ## 快速接入
16
-
17
- ```js
18
- // src/main.js
19
- import { createApp } from 'vue'
20
- import { createAppleUI } from 'apptify'
21
- import 'apptify/style.css'
22
- import App from './App.vue'
23
-
24
- createApp(App)
25
- .use(createAppleUI({ theme: 'system', motion: 'auto', persist: true }))
26
- .mount('#app')
27
- ```
28
-
29
- ```vue
30
- <!-- App.vue -->
31
- <template>
32
- <apple-provider>
33
- <apple-container>
34
- <apple-card title="个人资料">
35
- <apple-input v-model="name" label="姓名" />
36
- <template #actions>
37
- <apple-button @click="save">保存</apple-button>
38
- </template>
39
- </apple-card>
40
- </apple-container>
41
- </apple-provider>
42
- </template>
43
-
44
- <script>
45
- export default {
46
- data: () => ({ name: '' }),
47
- methods: {
48
- save() {
49
- this.$apple.notify('资料已保存', { tone: 'success' })
50
- },
51
- },
52
- }
53
- </script>
54
- ```
55
-
56
- `AppleProvider` 提供主题与弹层宿主,无需额外添加 `AppleOverlayHost`。插件全局注册组件,也支持命名导入并局部注册。嵌套 Provider 内可用 `appleKey` 注入局部上下文,或在 `setup()` 中使用 `useApple()`。
13
+ 安装后会自带所有组件说明的样例站。Bundle 包含以下组件之外的内容:
14
+ - 自动根据 src/views 生成 routes
15
+ - lucide icons
16
+ - 竖屏、触屏布尔状态
17
+ - 全局广播通道 `sendMessage`
18
+ - 错误页
19
+ - 外观设置页
20
+ - 各组件示例与源码等,有问题可以直接抄
57
21
 
58
22
  ## 常用 API
59
23
 
@@ -73,26 +37,12 @@ const confirmed = await dialog.result // 确认 true,取消 false,关闭 und
73
37
 
74
38
  Props、事件、插槽、自定义主题与弹层消息回传见[组件 API](docs/COMPONENTS.md)。
75
39
 
76
- ## 创建完整应用
40
+ ## 自动发布
77
41
 
78
- ```sh
79
- npm create apptify@latest ./my-app
80
- cd my-app
81
- npm install
82
- npm run dev
83
- ```
84
-
85
- 也可运行 `npx --package=apptify apptify-playground ./my-app`。
86
-
87
- 目标目录需为空。模板含导航、设置、账号表单、404 和组件示例,可直接编辑;账号功能需接入自己的后端。复制项目携带库归档,可独立安装。页面开发与部署见[应用模板](docs/PLAYGROUND-TEMPLATE.md)。
42
+ 推送到 `main` 后,GitHub Actions 会将尚未发布的 `apptify` 和 `create-apptify` 版本发布到 npm。发布前更新对应的 `package.json` 版本;已发布版本自动跳过,预发布版本使用 `next` 标签。
88
43
 
89
- ## 本地开发
90
-
91
- ```sh
92
- npm ci
93
- npm run dev
94
- ```
44
+ 在两个 npm 包的 Settings → Trusted Publisher 中选择 GitHub Actions,填写用户 `GARBAGELINUXGRAPHIC`、仓库 `apptify`、工作流文件 `npm-publish.yml`,允许 `npm publish`,环境名留空。使用 OIDC,无需配置或保存 npm token。
95
45
 
96
- 建议 Node.js 22 或 24 LTS。检查使用 `npm run typecheck`、`npm test`、`npm run build`、`npm run test:e2e` 和 `npm run test:package`;`npm pack` 自动构建并准备模板。生成器位于 [`packages/create-apptify`](packages/create-apptify/README.md),使用 `npm run test:create` 验证。历史验证记录见[验证文档](docs/VALIDATION.md)。
46
+ ## LICENSE
97
47
 
98
- MIT 许可。Apptify 不是 Apple 官方产品。
48
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apptify",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "description": "An Apple-inspired, responsive Vue 3 component library.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,7 +10,7 @@
10
10
  "preview": "vite preview --host 127.0.0.1"
11
11
  },
12
12
  "dependencies": {
13
- "apptify": "^0.5.4",
13
+ "apptify": "^0.6.0",
14
14
  "vue": "^3.5.0",
15
15
  "vue-router": "^4.6.4",
16
16
  "vuetify": "^3.9.0",
@@ -1,6 +1,6 @@
1
1
  {
2
- "createdAt": "2026-10-04T09:39:13.558Z",
3
- "libraryVersion": "0.5.4",
2
+ "createdAt": "2026-10-04T14:04:37.104Z",
3
+ "libraryVersion": "0.6.0",
4
4
  "files": {
5
5
  "index.html": "75f6d93e3962ddca97a13b666a04d98ba9b263b6c288171383db764215034fea",
6
6
  "public/images/SOURCES.md": "24ccb6625b1168e48550b7651e9fc20cb747324d87666d39199608c7db20541b",
@@ -17,8 +17,8 @@
17
17
  "public/images/sunset.jpg": "4ee91e88f1e341143a807ef48fdfc24d63aa045d99351b4ea8ad7efcfef6ef9b",
18
18
  "public/images/waterfall.jpg": "cfd55ff5f41d3a58e91793d0e26a9ca90c86760aa401da033fbfa77d3364f238",
19
19
  "src/App.vue": "1c794c9a04e5830cde04e2884a7ab3116e36283a9def63fc4d16cf9eea7151e0",
20
- "src/ComponentDemo.vue": "0c3b65ad2bec0da36d01451b0fd2e4c4e6bdb34da0beb2f0b813427fa2eeaa3d",
21
- "src/catalog.ts": "3aecd5190d37533ecd9427da3a2c9ed3b18b6f2f04a51c543ff88dfe52070e59",
20
+ "src/ComponentDemo.vue": "7f8ba3f3fc5c0c4a212383c6bd9d390271f5d9cc058802393ebd18b06be2aaa6",
21
+ "src/catalog.ts": "688145825314fed7dfb41a22c121a895f8114e3abf96dc812bc7c5186a16fa0c",
22
22
  "src/components/ComponentIndex.vue": "55c021899113b7fe26dcf42075f188a49d996e04a992ec9c86410eadc29e68a0",
23
23
  "src/components/DirectoryDialog.vue": "ec6191422dad07dff2d710748a3f024bf7828be8b9b70fa16ab474f36ed878d2",
24
24
  "src/components/PageFooter.vue": "5e9c1b4ba8866c05216993fb8d605fa826a09739a5ced15068b24334e4814fac",
@@ -46,7 +46,7 @@
46
46
  "src/views/component-docs/[component].vue": "3f505550dacb1d8151417df61d0598940aa7d218518179e532ddc962b0632a5e",
47
47
  "src/views/components.vue": "a19c4ca87a6a2ed43c4a6dc6eb52d3071733ce787024e1ab305db99b6acc6333",
48
48
  "src/views/index.vue": "9afc860da88aebe4879668784da24c657ecba19e5bc3ddad9dbcdd72f84db245",
49
- "src/views/settings.vue": "f5f9ca38f99d80fafab75fd33053715faa4265b998f32604af432497215a7c09",
49
+ "src/views/settings.vue": "1d36c866d1d5b1ec426d1254b31649e54585d3f3b4313ec8eb924f6a0eba456a",
50
50
  "tooling/playground-editor.mjs": "128b1ffa1b5660b04f7652130d5474c1f31686c2c8e0e2866f1ebde2594d0b5b",
51
51
  "tooling/prepare-input-source.mjs": "91bea35dcfa09735dbaeb5fbed7a70abfe447c3d133f4b4381e6ba0d0d4f9f6a"
52
52
  },
@@ -135,8 +135,8 @@
135
135
  "src/styles/overlays.css": "bb3d5f7bfc1b3e7a0ecf5ab4f632a65be3f496cb5de0e8c885bd6b6e5dc0f97a",
136
136
  "src/styles/ripple.css": "df0a5f661a862b5232bac256958f4bae6f555dd1f4a9c1432f61e85de71bdfea",
137
137
  "playground/App.vue": "1c794c9a04e5830cde04e2884a7ab3116e36283a9def63fc4d16cf9eea7151e0",
138
- "playground/ComponentDemo.vue": "0c3b65ad2bec0da36d01451b0fd2e4c4e6bdb34da0beb2f0b813427fa2eeaa3d",
139
- "playground/catalog.ts": "3aecd5190d37533ecd9427da3a2c9ed3b18b6f2f04a51c543ff88dfe52070e59",
138
+ "playground/ComponentDemo.vue": "7f8ba3f3fc5c0c4a212383c6bd9d390271f5d9cc058802393ebd18b06be2aaa6",
139
+ "playground/catalog.ts": "688145825314fed7dfb41a22c121a895f8114e3abf96dc812bc7c5186a16fa0c",
140
140
  "playground/components/ComponentIndex.vue": "55c021899113b7fe26dcf42075f188a49d996e04a992ec9c86410eadc29e68a0",
141
141
  "playground/components/DirectoryDialog.vue": "ec6191422dad07dff2d710748a3f024bf7828be8b9b70fa16ab474f36ed878d2",
142
142
  "playground/components/PageFooter.vue": "5e9c1b4ba8866c05216993fb8d605fa826a09739a5ced15068b24334e4814fac",
@@ -164,7 +164,7 @@
164
164
  "playground/views/component-docs/[component].vue": "3f505550dacb1d8151417df61d0598940aa7d218518179e532ddc962b0632a5e",
165
165
  "playground/views/components.vue": "a19c4ca87a6a2ed43c4a6dc6eb52d3071733ce787024e1ab305db99b6acc6333",
166
166
  "playground/views/index.vue": "9afc860da88aebe4879668784da24c657ecba19e5bc3ddad9dbcdd72f84db245",
167
- "playground/views/settings.vue": "f5f9ca38f99d80fafab75fd33053715faa4265b998f32604af432497215a7c09",
167
+ "playground/views/settings.vue": "1d36c866d1d5b1ec426d1254b31649e54585d3f3b4313ec8eb924f6a0eba456a",
168
168
  "public/images/SOURCES.md": "24ccb6625b1168e48550b7651e9fc20cb747324d87666d39199608c7db20541b",
169
169
  "public/images/airpods-max-orange.jpg": "f365fe9e374e2cede88f3e68487f6a773a767873978bcf20e4b21dee08aca966",
170
170
  "public/images/architecture.jpg": "928ea321954f0edabc1eed3c908c6b1fc93b247badcbd89d8dc5ba237420c26b",
@@ -182,8 +182,8 @@
182
182
  "docs/INPUT-WORKSHOP.md": "82ea6db156d2d9be4cfee233675b107c837aaa30a849f92010e988e1df7ce3a0",
183
183
  "docs/PLAYGROUND-TEMPLATE.md": "e476d6622e89d8cb04d9acdd54c090e5d559d666bb49f266bcf36d3f3bf8fe67",
184
184
  "docs/VALIDATION.md": "3c1755f4699d6b7d8febb44b2b0b00eb416b86559c26d8b69e46a1c98271af5f",
185
- "README.md": "95a66c9be54623335683af80d48e2415ae195e3797d624dfa69d042d6ea14958",
186
- "package.json": "905ddd2741ce948047ae576fd6715f048cbda60218618617d703397c76df7d0b",
185
+ "README.md": "1e24796cf31d3d93f34e4edd32b8e1629b1dfdc175f74df573e14b4eb12186ed",
186
+ "package.json": "677a70164d41c33517f36c661d773d17b4103d6e22fd55f8dc1df3c3bb3d2729",
187
187
  "index.html": "75f6d93e3962ddca97a13b666a04d98ba9b263b6c288171383db764215034fea",
188
188
  "vite.config.ts": "e2fee737d17c721cefaa33c955c6a7fcd47b46d51a95365768208eb16b608585",
189
189
  "vite.lib.config.ts": "f877c1772da70c9fed5174118ca36a0fc25fd46cce49b71280a5f91c4f8003a3",
@@ -123,7 +123,6 @@
123
123
  </apple-action-sheet>
124
124
  </template>
125
125
  <apple-pull-refresh v-else-if="name === 'ApplePullRefresh'" @refresh="refresh"><apple-list :items="[{label:'最新内容',value:'latest',description:refreshText},{label:'最近更新',value:'recent',description:'所有内容已同步'}]" /></apple-pull-refresh>
126
- <apple-infinite-scroll v-else-if="name === 'AppleInfiniteScroll'" :finished="itemsCount >= 12" @load="loadMore"><apple-list :items="Array.from({length:itemsCount}, (_, i) => ({label:`灵感收藏 ${i+1}`, value:i, description:'刚刚更新'}))" /></apple-infinite-scroll>
127
126
  <template v-else-if="name === 'AppleBackTop'"><apple-button :icon="icons.ArrowUp" variant="secondary" @click="toTop">回到顶部</apple-button><apple-back-top :threshold="0" :fixed="false" /></template>
128
127
  <template v-else-if="name === 'AppleFloatingGroup'"><apple-button variant="secondary" @click="notify('快捷操作已就绪')">快捷操作</apple-button><apple-card class="demo-floating-preview"><apple-provider><apple-floating-group :threshold="0" /></apple-provider></apple-card></template>
129
128
  <apple-stack v-else-if="name === 'AppleAutoSize'"><apple-switch v-model="checked" label="显示详情" /><apple-auto-size><apple-list :items="checked ? settings : settings.slice(0,1)" selectable /></apple-auto-size></apple-stack>
@@ -135,6 +134,7 @@
135
134
  </apple-transition>
136
135
  </div>
137
136
  </apple-stack>
137
+ <apple-infinite-scroll v-else-if="name === 'AppleInfiniteScroll'" :finished="itemsCount >= 12" @load="loadMore"><apple-list :items="Array.from({length:itemsCount}, (_, i) => ({label:`灵感收藏 ${i+1}`, value:i, description:'刚刚更新'}))" /></apple-infinite-scroll>
138
138
  </div>
139
139
  </template>
140
140
 
@@ -60,11 +60,11 @@ export const catalog: CatalogItem[] = [
60
60
  item('AppleMenu', '下拉菜单', '反馈', '按需展开的操作列表。', 'v-model · items[{label,value,disabled,danger,icon,description}] · label · selected | select(value,item)', '<apple-menu :items="actions" label="更多操作" @select="onAction" />'),
61
61
  item('AppleActionSheet', '动作面板', '移动交互', '熟悉、直接的移动操作。', 'v-model · title · message · items[{label,value,disabled,danger}] · cancelText | default slot {select(item),close(value)} | select(value,item)', '<apple-action-sheet v-model="open" title="选择操作" @select="onAction">\n <template #default="{ select }">\n <apple-button @click="select({ label: \'收藏\', value: \'favorite\' })">加入收藏</apple-button>\n </template>\n</apple-action-sheet>'),
62
62
  item('ApplePullRefresh', '下拉刷新', '移动交互', '轻轻下拉,获取新内容。', 'v-model(refreshing) · threshold · label · disabled | refresh(done)', '<apple-pull-refresh @refresh="async done => { await reload(); done() }">内容</apple-pull-refresh>'),
63
- item('AppleInfiniteScroll', '无限滚动', '移动交互', '浏览,不必被翻页打断。', 'loading · finished · error · disabled · distance | load(done), retry(done)', '<apple-infinite-scroll :finished="finished" @load="async done => { await loadMore(); done() }">内容</apple-infinite-scroll>'),
64
63
  item('AppleBackTop', '回到顶部', '移动交互', '回到开始,毫不费力。', 'threshold · label', '<apple-back-top :threshold="400" />'),
65
64
  item('AppleFloatingGroup', '悬浮操作组', '移动交互', '常用操作,触手可及。', 'backTop · threshold · target · label · motion | default', '<apple-floating-group :threshold="300" />'),
66
65
  item('AppleAutoSize', '自适应尺寸', '基础', '内容变化,布局随行。', 'axis(height|both) · motion | default', '<apple-auto-size><div v-if="expanded">详情内容</div></apple-auto-size>'),
67
66
  item('AppleTransition', '动效容器', '基础', '为页面保留连续感。', 'name(page|slide-x|slide-y|fade) · mode · appear · motion | default', '<apple-transition name="page"><router-view /></apple-transition>'),
67
+ item('AppleInfiniteScroll', '无限滚动', '移动交互', '浏览,不必被翻页打断。', 'loading · finished · error · disabled · distance | load(done), retry(done)', '<apple-infinite-scroll :finished="finished" @load="async done => { await loadMore(); done() }">内容</apple-infinite-scroll>'),
68
68
  ]
69
69
  export const groups = ['全部组件', '基础', '表单', '导航', '数据展示', '反馈', '移动交互']
70
70
 
@@ -38,7 +38,7 @@
38
38
  <div class="settings-section-heading"><div><h2 id="ripple-heading">点击波纹</h2><p>让每一次轻点,都有细微的回应。</p></div></div>
39
39
  <apple-switch :model-value="rippleAllowed && apple.ripple.value.enabled" :disabled="!rippleAllowed" label="点击波纹" :hint="rippleAllowed ? '减弱动效时自动关闭,可手动开启;偏好保存在这台设备上。' : '关闭动效时,无法开启点击波纹。'" @update:model-value="apple.ripple.value.set" />
40
40
  <div ref="rippleDemo" class="effect-demo ripple-sample" aria-label="波纹自动演示" @pointerenter="ripplePaused = true" @pointerleave="ripplePaused = false" @focusin="ripplePaused = true" @focusout="ripplePaused = false">
41
- <apple-button @click="rippleDemoClicks++">试试点击波纹</apple-button>
41
+ <apple-button variant="outline" @click="rippleDemoClicks++">试试点击波纹</apple-button>
42
42
  <span class="ripple-click-count">已点击 {{ rippleDemoClicks }} 次</span>
43
43
  </div>
44
44
  <div class="settings-card-foot">{{ rippleAllowed && apple.ripple.value.enabled ? '自动点击 · 也可以手动试一试' : '点击波纹已关闭 · 静态展示' }}</div>
@@ -127,7 +127,6 @@ const glassPreviewChoice = ref('1')
127
127
  const glassPreviewItems = [
128
128
  { label: 'Item 1', value: '1', description: 'Item 1' },
129
129
  { label: 'Item 2', value: '2', description: 'Item 2' },
130
- { label: 'Item 3', value: '3', description: 'Item 3' },
131
130
  ]
132
131
  function formatOpacity(value: number) { return `${Number(value.toFixed(1))}%` }
133
132
  function formatBlur(value: number) { return `${value}px` }
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict'
2
2
  import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
3
3
  import { join, resolve } from 'node:path'
4
4
  import { tmpdir } from 'node:os'
5
+ import { createServer } from 'node:net'
5
6
  import { createHash } from 'node:crypto'
6
7
  import { execFileSync, spawn } from 'node:child_process'
7
8
  import { fileURLToPath } from 'node:url'
@@ -70,7 +71,14 @@ if (hasConsumerTypes) {
70
71
  }
71
72
 
72
73
  async function serve() {
73
- const child = spawn(npm, ['run', 'dev', '--', '--host', '127.0.0.1', '--port', '0', '--strictPort'], { cwd: app, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' })
74
+ const reservation = createServer()
75
+ await new Promise((resolve, reject) => {
76
+ reservation.once('error', reject)
77
+ reservation.listen(0, '127.0.0.1', resolve)
78
+ })
79
+ const port = reservation.address().port
80
+ await new Promise((resolve, reject) => reservation.close(error => error ? reject(error) : resolve()))
81
+ const child = spawn(npm, ['run', 'dev', '--', '--host', '127.0.0.1', '--port', String(port), '--strictPort'], { cwd: app, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' })
74
82
  const stop = () => {
75
83
  if (process.platform !== 'win32') { try { process.kill(-child.pid, 'SIGTERM') } catch {} }
76
84
  else child.kill('SIGTERM')
@@ -121,7 +129,7 @@ try {
121
129
  const image = page.locator('#apple-image img').first()
122
130
  await image.scrollIntoViewIfNeeded()
123
131
  assert.equal(await image.evaluate(async image => { await image.decode(); return image.naturalWidth > 0 }), true)
124
- await page.locator('.component-index [aria-current]').first().waitFor()
132
+ await page.locator('.component-index [role=treeitem][aria-selected=true]').first().waitFor()
125
133
  })
126
134
  await checkPage(server.url, '/component-docs/apple-input', async page => {
127
135
  const frame = page.frameLocator('[data-testid="input-playground"] .vue-repl iframe')
@@ -135,8 +143,8 @@ try {
135
143
  await page.getByRole('slider', { name: '玻璃不透明度', exact: true }).waitFor()
136
144
  await page.getByRole('button', { name: '打开用户菜单', exact: true }).click()
137
145
  await page.getByRole('button', { name: /登录 Login/ }).click()
138
- await page.getByLabel('电子邮箱', { exact: true }).fill('template@example.com')
139
- await page.getByLabel('密码', { exact: true }).fill('template-password')
146
+ await page.locator('.user-auth-form').getByRole('textbox', { name: /^电子邮箱/ }).fill('template@example.com')
147
+ await page.locator('.user-auth-form').getByLabel(/^密码/).fill('template-password')
140
148
  assert.equal(await page.locator('.user-auth-form input[type=password]').count(), 1)
141
149
  await settledScreenshot(page, 'settings-login-320.png')
142
150
  })