apptify 0.6.2 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_LICENSES.txt +23 -0
  3. package/dist/apptify.css +1 -1
  4. package/dist/apptify.js +2395 -2451
  5. package/dist/components/AppleCard.vue.d.ts +1 -1
  6. package/dist/components/button.d.ts +3 -3
  7. package/dist/components/content.d.ts +81 -20
  8. package/dist/components/forms.d.ts +2 -2
  9. package/dist/components/foundation.d.ts +136 -14
  10. package/dist/components/mobile-image-viewer.d.ts +35 -12
  11. package/dist/components/navibar.d.ts +2 -2
  12. package/dist/components/overlays.d.ts +10 -59
  13. package/dist/components/scrollbar.d.ts +88 -0
  14. package/dist/components/speed-dial.d.ts +157 -0
  15. package/dist/core/context.d.ts +11 -0
  16. package/dist/core/device.d.ts +2 -0
  17. package/dist/core/image-gallery-motion.d.ts +2 -1
  18. package/dist/core/image-motion.d.ts +1 -1
  19. package/dist/core/layers.d.ts +1 -0
  20. package/dist/core/motion-driver.d.ts +40 -0
  21. package/dist/core/motion.d.ts +1 -1
  22. package/dist/core/ripple.d.ts +16 -0
  23. package/dist/core/scroll-motion.d.ts +6 -0
  24. package/dist/core/scrollbar.d.ts +14 -0
  25. package/dist/core/speed-dial.d.ts +21 -0
  26. package/dist/index.d.ts +240 -29
  27. package/docs/COMPONENTS.md +11 -7
  28. package/package.json +5 -7
  29. package/templates/playground/README.md +1 -1
  30. package/templates/playground/package.json +1 -2
  31. package/templates/playground/source-snapshot.json +300 -67
  32. package/templates/playground/src/App.vue +1 -0
  33. package/templates/playground/src/ComponentDemo.vue +30 -21
  34. package/templates/playground/src/catalog.ts +70 -65
  35. package/templates/playground/src/components/ComponentIndex.vue +10 -14
  36. package/templates/playground/src/components/MasonryFeed.vue +52 -0
  37. package/templates/playground/src/components/UserMenu.vue +1 -1
  38. package/templates/playground/src/components/forbidden-paper-scene.vue +367 -0
  39. package/templates/playground/src/components/forbidden-paper-story.ts +233 -0
  40. package/templates/playground/src/editor/ComponentPlayground.vue +91 -0
  41. package/templates/playground/src/editor/ConfigurableEditor.vue +1 -1
  42. package/templates/playground/src/editor/apple-input-source.ts +1 -1
  43. package/templates/playground/src/editor/component-metadata.ts +72 -0
  44. package/templates/playground/src/editor/component-playground.ts +163 -0
  45. package/templates/playground/src/editor/contract.ts +1 -1
  46. package/templates/playground/src/editor/documents.ts +11 -2
  47. package/templates/playground/src/editor/handbook.ts +22 -0
  48. package/templates/playground/src/editor/handbooks/apple-accordion.ts +66 -0
  49. package/templates/playground/src/editor/handbooks/apple-action-sheet.ts +72 -0
  50. package/templates/playground/src/editor/handbooks/apple-alert.ts +67 -0
  51. package/templates/playground/src/editor/handbooks/apple-aside.ts +32 -0
  52. package/templates/playground/src/editor/handbooks/apple-auto-size.ts +32 -0
  53. package/templates/playground/src/editor/handbooks/apple-autocomplete.ts +114 -0
  54. package/templates/playground/src/editor/handbooks/apple-avatar-group.ts +50 -0
  55. package/templates/playground/src/editor/handbooks/apple-avatar.ts +56 -0
  56. package/templates/playground/src/editor/handbooks/apple-back-top.ts +62 -0
  57. package/templates/playground/src/editor/handbooks/apple-badge.ts +62 -0
  58. package/templates/playground/src/editor/handbooks/apple-breadcrumbs.ts +38 -0
  59. package/templates/playground/src/editor/handbooks/apple-button.ts +92 -0
  60. package/templates/playground/src/editor/handbooks/apple-card.ts +112 -0
  61. package/templates/playground/src/editor/handbooks/apple-cascader.ts +102 -0
  62. package/templates/playground/src/editor/handbooks/apple-checkbox.ts +85 -0
  63. package/templates/playground/src/editor/handbooks/apple-color-picker.ts +79 -0
  64. package/templates/playground/src/editor/handbooks/apple-container.ts +26 -0
  65. package/templates/playground/src/editor/handbooks/apple-date-picker.ts +104 -0
  66. package/templates/playground/src/editor/handbooks/apple-dialog.ts +151 -0
  67. package/templates/playground/src/editor/handbooks/apple-divider.ts +38 -0
  68. package/templates/playground/src/editor/handbooks/apple-drawer.ts +151 -0
  69. package/templates/playground/src/editor/handbooks/apple-empty.ts +43 -0
  70. package/templates/playground/src/editor/handbooks/apple-floating-group.ts +51 -0
  71. package/templates/playground/src/editor/handbooks/apple-form-field.ts +69 -0
  72. package/templates/playground/src/editor/handbooks/apple-form.ts +76 -0
  73. package/templates/playground/src/editor/handbooks/apple-grid.ts +32 -0
  74. package/templates/playground/src/editor/handbooks/apple-image.ts +122 -0
  75. package/templates/playground/src/editor/handbooks/apple-infinite-scroll.ts +73 -0
  76. package/templates/playground/src/editor/handbooks/apple-link.ts +68 -0
  77. package/templates/playground/src/editor/handbooks/apple-list.ts +67 -0
  78. package/templates/playground/src/editor/handbooks/apple-menu.ts +72 -0
  79. package/templates/playground/src/editor/handbooks/apple-navibar.ts +100 -0
  80. package/templates/playground/src/editor/handbooks/apple-otp-input.ts +108 -0
  81. package/templates/playground/src/editor/handbooks/apple-overlay-host.ts +13 -0
  82. package/templates/playground/src/editor/handbooks/apple-pagination.ts +61 -0
  83. package/templates/playground/src/editor/handbooks/apple-popover.ts +112 -0
  84. package/templates/playground/src/editor/handbooks/apple-progress.ts +56 -0
  85. package/templates/playground/src/editor/handbooks/apple-provider.ts +32 -0
  86. package/templates/playground/src/editor/handbooks/apple-pull-refresh.ts +61 -0
  87. package/templates/playground/src/editor/handbooks/apple-radio-group.ts +91 -0
  88. package/templates/playground/src/editor/handbooks/apple-rate.ts +97 -0
  89. package/templates/playground/src/editor/handbooks/apple-scroll-bar.ts +15 -0
  90. package/templates/playground/src/editor/handbooks/apple-search.ts +49 -0
  91. package/templates/playground/src/editor/handbooks/apple-segmented-control.ts +85 -0
  92. package/templates/playground/src/editor/handbooks/apple-select.ts +91 -0
  93. package/templates/playground/src/editor/handbooks/apple-sheet.ts +151 -0
  94. package/templates/playground/src/editor/handbooks/apple-skeleton.ts +74 -0
  95. package/templates/playground/src/editor/handbooks/apple-slider.ts +103 -0
  96. package/templates/playground/src/editor/handbooks/apple-snackbar.ts +89 -0
  97. package/templates/playground/src/editor/handbooks/apple-speed-dial-item.ts +16 -0
  98. package/templates/playground/src/editor/handbooks/apple-speed-dial.ts +13 -0
  99. package/templates/playground/src/editor/handbooks/apple-spinner.ts +38 -0
  100. package/templates/playground/src/editor/handbooks/apple-stack.ts +44 -0
  101. package/templates/playground/src/editor/handbooks/apple-statistic.ts +69 -0
  102. package/templates/playground/src/editor/handbooks/apple-stepper.ts +91 -0
  103. package/templates/playground/src/editor/handbooks/apple-steps.ts +61 -0
  104. package/templates/playground/src/editor/handbooks/apple-switch.ts +79 -0
  105. package/templates/playground/src/editor/handbooks/apple-tab-bar.ts +66 -0
  106. package/templates/playground/src/editor/handbooks/apple-table.ts +176 -0
  107. package/templates/playground/src/editor/handbooks/apple-tabs.ts +72 -0
  108. package/templates/playground/src/editor/handbooks/apple-tag.ts +56 -0
  109. package/templates/playground/src/editor/handbooks/apple-textarea.ts +109 -0
  110. package/templates/playground/src/editor/handbooks/apple-timeline.ts +44 -0
  111. package/templates/playground/src/editor/handbooks/apple-tooltip.ts +45 -0
  112. package/templates/playground/src/editor/handbooks/apple-transition.ts +44 -0
  113. package/templates/playground/src/editor/handbooks/apple-tree.ts +84 -0
  114. package/templates/playground/src/editor/handbooks/apple-upload.ts +126 -0
  115. package/templates/playground/src/editor/playground-examples.ts +105 -0
  116. package/templates/playground/src/images.ts +3 -0
  117. package/templates/playground/src/router/index.ts +19 -2
  118. package/templates/playground/src/style.css +4 -4
  119. package/templates/playground/src/views/403.vue +65 -0
  120. package/templates/playground/src/views/component-docs/[component].vue +11 -14
  121. package/templates/playground/src/views/components.vue +11 -41
  122. package/templates/playground/src/views/index.vue +3 -1
  123. package/templates/playground/src/views/settings.vue +5 -2
  124. package/templates/playground/tooling/playground-editor.mjs +4 -3
  125. package/tooling/playground-editor.mjs +4 -3
  126. package/tooling/prepare-playground.mjs +3 -3
  127. package/tooling/test-playground-package.mjs +10 -0
  128. package/dist/components/gallery-scrollbar.d.ts +0 -46
  129. package/dist/core/desktop-image-enter.d.ts +0 -6
  130. package/dist/core/desktop-image-return.d.ts +0 -1
  131. package/templates/playground/src/components/DirectoryDialog.vue +0 -20
@@ -6,9 +6,8 @@
6
6
  <aside class="directory-index sidebar" aria-label="组件目录">
7
7
  <header class="directory-header">
8
8
  <span>组件目录</span>
9
- <div class="directory-search"><apple-search v-model="directorySearch" label="搜索组件目录" placeholder="名称、标签或描述" /></div>
10
9
  </header>
11
- <div class="directory-tree"><ComponentIndex v-model:expanded="expandedGroups" :active-group="activeGroup" :active-id="activeId" :search="directorySearch" /></div>
10
+ <div class="directory-tree"><ComponentIndex v-model:expanded="expandedGroups" :active-group="activeGroup" :active-id="activeId" /></div>
12
11
  </aside>
13
12
  <div class="components-content">
14
13
  <div class="feed-toolbar">
@@ -21,54 +20,40 @@
21
20
  <div ref="feed" class="component-feed">
22
21
  <section v-for="(section, index) in sections" :id="section.id" :key="section.id" class="feed-group feed-anchor" :data-group="section.id" :aria-labelledby="`${section.id}-heading`">
23
22
  <header class="feed-group-heading"><span>0{{ index + 1 }}</span><h2 :id="`${section.id}-heading`">{{ section.label }}</h2><small>{{ section.items.length }} 个组件</small></header>
24
- <div class="feed-cards">
23
+ <MasonryFeed class="feed-cards" @layout="schedulePosition">
25
24
  <apple-card v-for="item in section.items" :id="componentId(item.name)" :key="item.name" class="component-card feed-anchor" :data-group="section.id" :aria-labelledby="`${componentId(item.name)}-heading`">
26
25
  <header class="component-card-heading"><div><h3 :id="`${componentId(item.name)}-heading`"><router-link :to="`#${componentId(item.name)}`">{{ item.label }}<Hash :size="14" /></router-link></h3><p>{{ item.description }}</p></div><code>{{ componentId(item.name) }}</code></header>
27
26
  <section class="detail-preview" :class="{ 'mobile-preview': previewMode === 'mobile' }" :aria-label="`${item.label}预览`"><ComponentDemo :name="item.name" @navigate="navigateGroup" /></section>
28
- <ComponentDocumentLink v-if="documentPaths[item.name]" :href="documentPaths[item.name]" label="代码与 API" />
29
- <apple-accordion v-else class="component-source" :items="sourceSections">
30
- <template #item>
31
- <div class="code-view"><div class="code-toolbar"><span>使用示例</span><apple-link @click="copy(item.code)">复制代码</apple-link></div><pre><code>{{ item.code }}</code></pre><h4>接口</h4><p class="api-line">{{ item.api }}</p></div>
32
- </template>
33
- </apple-accordion>
27
+ <ComponentDocumentLink :href="documentPaths[item.name]" label="代码与 API" />
34
28
  </apple-card>
35
- </div>
29
+ </MasonryFeed>
36
30
  </section>
37
31
  </div>
38
32
  </div>
39
33
  </div>
40
34
  </apple-container>
41
35
  </main>
42
- <DirectoryDialog v-model="mobileIndex" title="组件目录">
43
- <div class="directory-search"><apple-search v-model="directorySearch" label="搜索组件目录" placeholder="名称、标签或描述" /></div>
44
- <ComponentIndex v-model:expanded="expandedGroups" :active-group="activeGroup" :active-id="activeId" :search="directorySearch" @navigate="mobileIndex = false" />
45
- </DirectoryDialog>
46
36
  </template>
47
37
 
48
38
  <script setup lang="ts">
49
39
  import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
50
40
  import { useRoute, useRouter } from 'vue-router'
51
41
  import { Hash, Monitor, Smartphone } from 'lucide-vue-next'
52
- import { useApple } from 'apptify'
53
42
  import { catalog, sections, componentId } from '../catalog'
54
43
  import ComponentDemo from '../ComponentDemo.vue'
55
44
  import ComponentIndex from '../components/ComponentIndex.vue'
56
- import DirectoryDialog from '../components/DirectoryDialog.vue'
45
+ import MasonryFeed from '../components/MasonryFeed.vue'
57
46
  import ComponentDocumentLink from '../editor/ComponentDocumentLink.vue'
58
47
  import { componentDocuments, componentDocumentPath } from '../editor/documents'
59
48
 
60
49
  const documentPaths = Object.fromEntries(Object.entries(componentDocuments).map(([slug, document]) => [document.name, componentDocumentPath(slug)]))
61
50
  const route = useRoute()
62
51
  const router = useRouter()
63
- const apple = useApple()
64
52
  const feed = ref<HTMLElement>()
65
53
  const activeGroup = ref(sections[0].id)
66
54
  const activeId = ref('')
67
55
  const previewMode = ref('desktop')
68
- const mobileIndex = ref(false)
69
- const directorySearch = ref('')
70
56
  const expandedGroups = ref(sections.map(section => section.id))
71
- const sourceSections = [{ label: '代码与 API', value: 'source' }]
72
57
  let anchors: HTMLElement[] = []
73
58
  let frame = 0
74
59
  let observer: ResizeObserver | undefined
@@ -92,14 +77,10 @@ function navigateGroup(label: string) {
92
77
  const section = sections.find(section => section.label === label)
93
78
  void router.push(section ? `/components#${section.id}` : '/components')
94
79
  }
95
- async function copy(value: string) {
96
- try { await navigator.clipboard.writeText(value); apple.notify('已复制', { tone: 'success' }) }
97
- catch { apple.notify('无法访问剪贴板,请手动选择代码复制', { tone: 'warning' }) }
98
- }
99
- watch(() => route.hash, () => { mobileIndex.value = false; schedulePosition() })
80
+ watch(() => route.hash, schedulePosition)
100
81
  watch(activeId, async () => {
101
82
  await nextTick()
102
- const sidebar = document.querySelector<HTMLElement>('.directory-tree')
83
+ const sidebar = document.querySelector<HTMLElement>('.directory-tree .apple-tree-scroll')
103
84
  const current = sidebar?.querySelector<HTMLElement>('.apple-tree__row.is-selected')
104
85
  if (!sidebar || !current) return
105
86
  const item = current.getBoundingClientRect(), bounds = sidebar.getBoundingClientRect()
@@ -126,23 +107,12 @@ onBeforeUnmount(() => {
126
107
  .directory-index { display: flex; flex-direction: column; overflow: hidden; }
127
108
  .directory-header { flex-shrink: 0; }
128
109
  .directory-header > span { color: var(--apple-secondary); font-size: 12px; display: block; margin: 0 0 12px 8px; }
129
- .directory-tree { min-height: 0; overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; }
110
+ .directory-tree { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
130
111
  @media (max-width: 900px) { .directory-index { display: none; } }
131
- .directory-search { min-width: 0; margin: 0 4px 16px; }
132
- .directory-search :deep(.apple-field__label) { font-size: 11px; }
133
- .directory-search :deep(input) { min-width: 0; font-size: 12px; }
134
112
  .components-page > * { max-width: none; }
135
- .feed-cards { column-width: 400px; column-gap: 20px; }
113
+ .feed-anchor { scroll-margin-top: 96px; }
136
114
  .feed-group-heading { padding-bottom: 20px; }
137
- .component-card { display: inline-block; vertical-align: top; width: 100%; break-inside: avoid; margin-bottom: 20px; padding: 0; }
115
+ .component-card { width: 100%; padding: 0; }
138
116
  .component-card :deep(> .apple-card__body) { padding: 0; border-radius: inherit; }
139
- .component-card :deep(> .apple-card__body > .documentation-entry),
140
- .component-card :deep(> .apple-card__body > .component-source) { border-radius: 0 0 18px 18px; overflow: hidden; }
141
- .component-source :deep(.apple-accordion__item) { border: 0; }
142
- .component-source :deep(.apple-accordion__item > h3) { font-size: 11px; font-weight: 400; }
143
- .component-source :deep(.apple-accordion__item > h3 > button) { min-height: 44px; gap: 8px; padding: 13px 28px; color: var(--apple-secondary); }
144
- .component-source :deep(.apple-accordion__content) { padding: 0; }
145
- @media (max-width: 800px) {
146
- .component-source :deep(.apple-accordion__item > h3 > button) { padding-inline: 18px; }
147
- }
117
+ .component-card :deep(> .apple-card__body > .documentation-entry) { border-radius: 0 0 18px 18px; overflow: hidden; }
148
118
  </style>
@@ -20,7 +20,7 @@
20
20
  <apple-card class="specimen"><div class="specimen-caption"><span>02 / 控制</span></div><h2>点到,即止。</h2><div class="button-specimens"><apple-button :icon="arrowIcon" @click="$apple.notify('操作已完成',{tone:'success'})">继续探索</apple-button><apple-button variant="secondary" @click="$apple.notify('已加入收藏')">加入收藏</apple-button><apple-button variant="outline" @click="$router.push('/components#apple-button')">了解更多</apple-button><div class="icon-button-row"><apple-button variant="secondary" :icon="plusIcon" icon-only label="添加" @click="$apple.notify('已添加')" /><apple-button :variant="liked?'primary':'secondary'" :icon="heartIcon" icon-only :label="liked ? '取消收藏' : '收藏'" :aria-pressed="liked" @click="liked=!liked" /><apple-button variant="secondary" :icon="downloadIcon" icon-only label="浏览组件示例" @click="$router.push('/components')" /></div></div><div class="specimen-foot"><code>apple-button</code><span>5 种样式</span></div></apple-card>
21
21
  <apple-card class="specimen"><div class="specimen-caption"><span>03 / 偏好</span></div><h2>恰好,是你的。</h2><div class="preference-demo"><apple-segmented-control v-model="period" :items="periods" label="统计周期" /><div class="setting-line"><div><strong>消息通知</strong><small>不错过重要的事</small></div><apple-switch v-model="notices" aria-label="消息通知" /></div><div class="setting-line"><div><strong>自动同步</strong><small>在所有设备上保持一致</small></div><apple-switch v-model="sync" aria-label="自动同步" /></div><apple-slider v-model="volume" label="音量" /><div class="check-row"><apple-checkbox v-model="agreed" label="记住我的偏好" /></div></div></apple-card>
22
22
  <apple-card class="specimen form-specimen"><div class="specimen-caption"><span>04 / 输入</span></div><h2>开始一段对话。</h2><apple-form @submit="$apple.notify(`你好,${name || '新朋友'}!`,{tone:'success'})"><apple-input v-model="name" label="你的名字" placeholder="怎么称呼你" autocomplete="name" clearable required /><apple-input v-model="email" label="电子邮箱" type="email" placeholder="name@example.com" autocomplete="email" required /><apple-button type="submit">加入我们<ArrowRight :size="15" /></apple-button></apple-form></apple-card>
23
- <apple-card class="specimen media-specimen"><div class="specimen-caption"><span>05 / 媒体</span></div><h2>靠近一点,再一点。</h2><apple-image :gallery="[{ src: '/images/lake.jpg', alt: '山间湖泊和木屋' }, { src: '/images/airpods-max-orange.jpg', alt: '橙色耳机' }]" aspect-ratio="4/3" /><div class="specimen-foot"><code>apple-image</code><span>山间的一刻</span></div></apple-card>
23
+ <apple-card class="specimen media-specimen"><div class="specimen-caption"><span>05 / 媒体</span></div><h2>靠近一点,再一点。</h2><apple-image :gallery="[{ src: imageUrl('lake.jpg'), alt: '山间湖泊和木屋' }, { src: imageUrl('airpods-max-orange.jpg'), alt: '橙色耳机' }]" aspect-ratio="4/3" /><div class="specimen-foot"><code>apple-image</code><span>山间的一刻</span></div></apple-card>
24
24
  <apple-card class="specimen feedback-specimen"><div class="specimen-caption"><span>06 / 反馈</span></div><h2>每一步,都有回应。</h2><apple-alert title="所有更改已保存" tone="success" /><div class="progress-demo"><div><span>正在同步</span><span>{{progress}}%</span></div><apple-progress :model-value="progress" label="正在同步" /></div><apple-avatar-group :items="[{name:'林初'},{name:'Alex'},{name:'Taylor'},{name:'Quinn'}]" /><div class="feedback-actions"><apple-button variant="secondary" size="small" @click="openDemoDialog">打开对话框</apple-button><apple-link as="button" @click="$apple.notify('所有更改已保存',{tone:'success'});progress=Math.min(100,progress+10)">显示提示</apple-link></div></apple-card>
25
25
  </div>
26
26
  </section>
@@ -34,6 +34,7 @@
34
34
  </template>
35
35
 
36
36
  <script lang="ts">
37
+ import { imageUrl } from '../images'
37
38
  import { defineComponent, markRaw } from 'vue'
38
39
  import { ArrowRight, ArrowUpRight, Download, Heart, Plus } from 'lucide-vue-next'
39
40
  import { catalog } from '../catalog'
@@ -57,6 +58,7 @@ export default defineComponent({
57
58
  },
58
59
  },
59
60
  methods: {
61
+ imageUrl,
60
62
  openDemoDialog() { this.$apple.dialog({title:'一切,就从这里开始。',message:'试试这些组件,找到适合你的交互。',confirmText:'好的',cancelText:'稍后'}) },
61
63
  },
62
64
  })
@@ -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 variant="outline" @click="rippleDemoClicks++">试试点击波纹</apple-button>
41
+ <apple-list :items="rippleDemoItems" selectable label="点击波纹演示" @select="rippleDemoClicks++" />
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>
@@ -93,6 +93,7 @@ const rippleDemo = ref<HTMLElement>()
93
93
  const motionDemoOpened = ref<string[]>([])
94
94
  const motionDemoItems = [{ label: '预览效果', value: 'preview', content: '折叠面板与所有组件的动画速度、数量都会跟随你选择的动效强度。' }]
95
95
  const rippleDemoClicks = ref(0)
96
+ const rippleDemoItems = [{ label: '试试点击波纹', value: 'ripple-preview' }]
96
97
  const motionPaused = ref(false)
97
98
  const ripplePaused = ref(false)
98
99
  let demoTimer: number | undefined
@@ -193,13 +194,15 @@ function reset() {
193
194
  .motion-sample :deep(.apple-accordion h3) { font-size: 14px; }
194
195
  .motion-sample :deep(.apple-accordion__content) { font-size: 12px; }
195
196
  .ripple-sample { flex: 1; max-height: 180px; margin-block: auto; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 16px; padding: 20px; }
197
+ .ripple-sample :deep(.apple-list-size) { width: 100%; border-radius: 12px; background: var(--apple-surface); }
198
+ .ripple-sample :deep(.apple-list__item) { padding-inline: 16px; }
196
199
  .ripple-click-count { color: var(--apple-secondary); font-size: 11px; }
197
200
  .glass-sample { min-height: 156px; display: grid; place-items: center; margin: 0 0 20px; isolation: isolate; }
198
201
  .glass-sample-colors { position: absolute; inset: -40px; display: flex; align-items: center; justify-content: center; gap: 20px; animation: sample-glass 6s ease-in-out infinite; }
199
202
  .glass-sample-colors span { width: 95px; height: 95px; flex-shrink: 0; border-radius: 28px; background: #82b6ef; transform: rotate(-20deg); }
200
203
  .glass-sample-colors span:nth-child(2) { background: #e9aa99; width: 72px; height: 120px; transform: rotate(25deg); }
201
204
  .glass-sample-colors span:nth-child(3) { background: #ada1e2; border-radius: 50%; }
202
- .glass-sample-panel { position: relative; display: grid; grid-template-columns: 20px 1fr; gap: 12px; width: 168px; padding: 18px; border: 1px solid var(--apple-border); border-radius: 12px; background: rgb(var(--apple-glass-rgb, 255 255 255) / var(--apple-glass-opacity, .3)); backdrop-filter: blur(var(--apple-glass-blur, 12px)) saturate(2); -webkit-backdrop-filter: blur(var(--apple-glass-blur, 12px)) saturate(2); font-size: 12px; }
205
+ .glass-sample-panel { position: relative; display: grid; grid-template-columns: 20px 1fr; gap: 12px; width: 168px; padding: 18px; border: 1px solid var(--apple-border); border-radius: 12px; background: rgb(var(--apple-glass-rgb, 255 255 255) / var(--apple-glass-opacity, .5)); backdrop-filter: blur(var(--apple-glass-blur, 12px)) saturate(2); -webkit-backdrop-filter: blur(var(--apple-glass-blur, 12px)) saturate(2); font-size: 12px; }
203
206
  .glass-sample-panel i { grid-column: 1 / -1; height: 5px; width: 100%; border-radius: 4px; background: currentColor; opacity: .15; }
204
207
  .glass-sample-panel i:last-child { width: 65%; }
205
208
  @keyframes sample-glass { 0%, 100% { transform: translateX(-32px) rotate(-8deg); } 50% { transform: translateX(32px) rotate(8deg); } }
@@ -1,7 +1,7 @@
1
1
  import { build } from 'esbuild'
2
2
  import { readFile } from 'node:fs/promises'
3
3
  import { createRequire } from 'node:module'
4
- import { resolve } from 'node:path'
4
+ import { resolve, sep } from 'node:path'
5
5
  import { existsSync } from 'node:fs'
6
6
  import { createHash } from 'node:crypto'
7
7
  import { parse, compileScript, compileStyle } from 'vue/compiler-sfc'
@@ -74,8 +74,9 @@ export default function playgroundEditor({ appDir = 'playground', libraryDir = '
74
74
  return { code, map: null }
75
75
  },
76
76
  async handleHotUpdate(ctx) {
77
- const source = libraryDir ? resolve(config.root, libraryDir) + '/' : undefined
78
- if (!(source && ctx.file.startsWith(source)) && ctx.file !== resolve(config.root, appDir, 'editor/runtime.ts')) return
77
+ const source = libraryDir ? resolve(config.root, libraryDir) + sep : undefined
78
+ const changedFile = resolve(ctx.file)
79
+ if (!(source && changedFile.startsWith(source)) && changedFile !== resolve(config.root, appDir, 'editor/runtime.ts')) return
79
80
  // Preview resources are an in-memory bundle, separate from Vite's normal CSS HMR.
80
81
  // Rebuild it when library files change, then recreate the preview realm.
81
82
  await rebuild()
@@ -1,7 +1,7 @@
1
1
  import { build } from 'esbuild'
2
2
  import { readFile } from 'node:fs/promises'
3
3
  import { createRequire } from 'node:module'
4
- import { resolve } from 'node:path'
4
+ import { resolve, sep } from 'node:path'
5
5
  import { existsSync } from 'node:fs'
6
6
  import { createHash } from 'node:crypto'
7
7
  import { parse, compileScript, compileStyle } from 'vue/compiler-sfc'
@@ -74,8 +74,9 @@ export default function playgroundEditor({ appDir = 'playground', libraryDir = '
74
74
  return { code, map: null }
75
75
  },
76
76
  async handleHotUpdate(ctx) {
77
- const source = libraryDir ? resolve(config.root, libraryDir) + '/' : undefined
78
- if (!(source && ctx.file.startsWith(source)) && ctx.file !== resolve(config.root, appDir, 'editor/runtime.ts')) return
77
+ const source = libraryDir ? resolve(config.root, libraryDir) + sep : undefined
78
+ const changedFile = resolve(ctx.file)
79
+ if (!(source && changedFile.startsWith(source)) && changedFile !== resolve(config.root, appDir, 'editor/runtime.ts')) return
79
80
  // Preview resources are an in-memory bundle, separate from Vite's normal CSS HMR.
80
81
  // Rebuild it when library files change, then recreate the preview realm.
81
82
  await rebuild()
@@ -50,7 +50,7 @@ async function adapt(directory) {
50
50
  source = source.replace('to="/" aria-label="Apptify 首页"', ':to="homePath" aria-label="Apptify 首页"')
51
51
  source = source.replace(navigationEnd, "].filter(item => router.getRoutes().some(page => page.path === item.href))\nconst homePath = navigation[0]?.href ?? '/'\n\nfunction navigate")
52
52
  }
53
- if (path === join(output, 'src/views/[...all].vue')) {
53
+ if (['[...all].vue', '403.vue'].some(view => path === join(output, 'src/views', view))) {
54
54
  source = source.replace('to="/" custom', ':to="homePath" custom')
55
55
  source = source.replace('const router = useRouter()', "const router = useRouter()\nconst homePath = router.getRoutes().some(page => page.path === '/') ? '/' : '/settings'")
56
56
  source = source.replace("else void router.push('/')", 'else void router.push(homePath)')
@@ -65,7 +65,7 @@ const manifest = {
65
65
  scripts: { dev: 'vite', typecheck: 'vue-tsc --noEmit', build: 'vue-tsc --noEmit && vite build', preview: 'vite preview --host 127.0.0.1' },
66
66
  dependencies: {
67
67
  apptify: '^' + library.version, vue: library.peerDependencies.vue,
68
- 'vue-router': library.devDependencies['vue-router'], vuetify: library.peerDependencies.vuetify,
68
+ 'vue-router': library.devDependencies['vue-router'],
69
69
  'lucide-vue-next': library.dependencies['lucide-vue-next'],
70
70
  },
71
71
  devDependencies: Object.fromEntries(['@vitejs/plugin-vue', 'typescript', 'vite', 'vite-plugin-pages', 'vue-tsc', '@vue/repl', 'es-module-shims', 'esbuild', 'postcss'].map(name => [name, library.devDependencies[name]])),
@@ -92,7 +92,7 @@ await writeFile(join(output, 'gitignore'), 'node_modules/\ndist/\n.env.local\n')
92
92
  await writeFile(join(output, 'README.md'), [
93
93
  "# Apptify 应用模板",
94
94
  "",
95
- "可编辑的 Vue 应用,包含导航、账号表单、外观设置、404 与组件示例。建议 Node.js 22 或 24 LTS。",
95
+ "可编辑的 Vue 应用,包含导航、账号表单、外观设置、403、404 与组件示例。建议 Node.js 22 或 24 LTS。",
96
96
  "",
97
97
  "```sh",
98
98
  "npm install",
@@ -114,6 +114,14 @@ async function checkPage(baseURL, path, check) {
114
114
  assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Page overflow: ' + path)
115
115
  await page.close()
116
116
  }
117
+ async function checkTableHandbook(page) {
118
+ await expect(page.getByTestId('component-playground')).toHaveAttribute('data-component', 'AppleTable')
119
+ const frame = page.frameLocator('.vue-repl iframe')
120
+ await frame.getByRole('button', { name: '评分', exact: true }).click()
121
+ await expect(frame.locator('tbody tr').first()).toContainText('Alex')
122
+ await expect(frame.getByRole('textbox', { name: '事件记录内容' })).toHaveValue(/update:sortBy "score"/)
123
+ await expect(page.getByTestId('component-document').locator('iframe')).toHaveCount(0)
124
+ }
117
125
  let server
118
126
  try {
119
127
  server = await serve()
@@ -138,6 +146,7 @@ try {
138
146
  await frame.locator('.apple-input-wrap input').first().fill('Template src preview')
139
147
  await expect(frame.locator('.apple-input-wrap input').first()).toHaveValue('Template src preview')
140
148
  })
149
+ await checkPage(server.url, '/component-docs/apple-table', checkTableHandbook)
141
150
  await checkPage(server.url, '/settings', async page => {
142
151
  await page.setViewportSize({ width: 320, height: 900 })
143
152
  await page.getByRole('button', { name: '深色', exact: true }).click()
@@ -174,6 +183,7 @@ try {
174
183
  run(['run', 'typecheck'], app)
175
184
  run(['run', 'build'], app)
176
185
  server = await serve()
186
+ await checkPage(server.url, '/component-docs/apple-table', checkTableHandbook)
177
187
  await checkPage(server.url, '/settings', async page => {
178
188
  assert.equal(await page.locator('.site-nav a.apple-navibar__item').count(), 1)
179
189
  assert.equal(await page.locator('.site-nav .brand').getAttribute('href'), '/settings')
@@ -1,46 +0,0 @@
1
- import { type PropType } from 'vue';
2
- export declare const GalleryScrollbar: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
3
- target: {
4
- type: PropType<() => HTMLElement | undefined>;
5
- required: true;
6
- };
7
- controls: {
8
- type: StringConstructor;
9
- required: true;
10
- };
11
- }>, {}, {
12
- box: HTMLElement | null;
13
- width: number;
14
- total: number;
15
- left: number;
16
- observer: ResizeObserver | null;
17
- mutations: MutationObserver | null;
18
- drag: {
19
- id: number;
20
- x: number;
21
- left: number;
22
- snap: string;
23
- } | null;
24
- }, {
25
- maximum(): number;
26
- progress(): number;
27
- }, {
28
- observe(): void;
29
- measure(): void;
30
- scroll(left: number): void;
31
- start(event: PointerEvent): void;
32
- move(event: PointerEvent): void;
33
- end(event?: PointerEvent): void;
34
- key(event: KeyboardEvent): void;
35
- }, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "interaction"[], "interaction", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
36
- target: {
37
- type: PropType<() => HTMLElement | undefined>;
38
- required: true;
39
- };
40
- controls: {
41
- type: StringConstructor;
42
- required: true;
43
- };
44
- }>> & Readonly<{
45
- onInteraction?: ((...args: any[]) => any) | undefined;
46
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,6 +0,0 @@
1
- import { type PhotoGeometry } from './image-geometry';
2
- export declare const interruptedPhotoEnter: WeakMap<HTMLElement, {
3
- geometry: PhotoGeometry;
4
- background: string;
5
- }>;
6
- export declare function enterDesktopPhoto(panel: HTMLElement, thumbnail: HTMLImageElement | null, motion: string, signal: AbortSignal): Promise<void>;
@@ -1 +0,0 @@
1
- export declare function returnDesktopPhoto(panel: HTMLElement, thumbnail: HTMLImageElement | null, motion: string, signal: AbortSignal): Promise<void>;
@@ -1,20 +0,0 @@
1
- <template>
2
- <apple-floating-group label="页面导航">
3
- <apple-button class="directory-toggle" variant="secondary" icon-only :icon="Menu" :label="`展开${title}`" aria-haspopup="dialog" :aria-expanded="modelValue" @click="modelValue = true" />
4
- </apple-floating-group>
5
- <apple-dialog v-model="modelValue" :title="title" :show-footer="false" width="480px">
6
- <div class="directory-dialog-content"><slot /></div>
7
- </apple-dialog>
8
- </template>
9
-
10
- <script setup lang="ts">
11
- import { Menu } from 'lucide-vue-next'
12
- defineProps<{ title: string }>()
13
- const modelValue = defineModel<boolean>({ default: false })
14
- </script>
15
-
16
- <style scoped>
17
- .directory-toggle { display: none; order: 1001; width: 44px; height: 44px; padding: 0; border-radius: 50%; box-shadow: var(--apple-shadow); }
18
- .directory-dialog-content { min-width: 0; }
19
- @media (max-width: 900px) { .directory-toggle { display: inline-flex; } }
20
- </style>