npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zh40s05/zepp-style-list

v0.1.5

Published

ZeppOS 系统风格列表控件 — 按键焦点、触屏焦点、表冠实时焦点切换与滚动吸附 / System-style list control with key focus, touch focus, real-time crown focus switching and scroll snapping

Readme

Zepp-Style-List — ZeppOS 系统风格列表控件 / ZeppOS System-style List Component

中文 | English


界面预览 / Screenshots

中文

是什么

Zepp-Style-List(简称 ZSList) 将 ZeppOS 系统风格列表页常见的页面结构封装成一个页面级组合控件,适合需要系统风格列表、物理按键焦点、触屏点击、表冠/滚轮滚动和圆屏/方屏适配的小程序页面:

  1. 系统风格行布局TEXT / SWITCH / CHECKBOX / RADIO / CATEGORY / FOOTER / IMAGE
  2. 物理按键焦点 — UP/DOWN 环绕导航,SELECT/HOME 触发当前项
  3. 触屏反馈 — 可点击项带按下遮罩,图片项带独立双描边焦点
  4. 滚动落焦 — 手指滑动或表冠/滚轮滚动结束后,焦点落到屏幕中心附近的条目
  5. 圆屏/方屏自动适配 — 根据 getDeviceInfo().screenShape 自动选择 layout profile,并按屏幕宽度缩放

它不是 ZeppOS 官方内置 widget,但使用方式刻意接近官方 createWidget(widget.X, Param)

const list = createListPage(Param)
const item = list.createWidget(listWidget.SWITCH, RowParam)

安装

# npm
npm install @zh40s05/zepp-style-list

# 手动复制
cp zslist.js your-app/utils/zslist.js

资源文件需要放入小程序 assets/default.r/。如果同时支持方屏目标,也需要放入 assets/default.s/

快速开始

import { showToast } from '@zos/interaction'
import { createListPage, listWidget } from '../utils/zslist'

Page({
  build() {
    const list = createListPage({
      header: { text: '设置' },
      footer: { button: true, click_func: () => showToast({ content: '帮助' }) },
    })

    list.createWidget(listWidget.TEXT, {
      title: '普通文本条目',
    })

    list.createWidget(listWidget.TEXT, {
      title: '可点击文本条目',
      clickable: true,
      click_func: () => showToast({ content: '点击' }),
    })

    list.createWidget(listWidget.SWITCH, {
      title: '开关条目',
      checked: true,
      checked_change_func: (checked) => showToast({ content: checked ? '开' : '关' }),
    })

    list.mount()
    this.list = list
  },
})

mount() 会创建默认 footer、焦点层、点击遮罩并注册物理按键。通常在所有 createWidget() 调用完成后执行一次。

API

| 导出 | 说明 | |---|---| | createListPage(param) | 创建列表页面控件 | | listWidget | 子控件类型常量,类似官方 widget | | listProp | 属性常量,用于 getProperty / setProperty | | ListPage | 列表页面类,通常不需要直接实例化 | | zslistDispose() | 手动清理当前列表实例;一般只在高级场景使用 |

createListPage 参数

| 参数 | 说明 | 默认 | |---|---|---| | header | 页头配置;传 false 不创建 header | 不创建 | | footer | 页脚配置;传 false 不创建 footer | {} | | hideStatusBar | 创建页面时调用 setStatusBarVisible(false) 隐藏系统状态栏;传 false 保持状态栏当前可见性 | true | | touchScrollStep | 触屏 scroll_frame_func.yoffset 到内部滚动位置的倍率;调节手指滑动落焦距离 | 1 | | crownEnable | 是否注册 onDigitalCrown 处理表冠/滚轮滚动 | true | | crownStep | 表冠/滚轮 degree 到滚动像素的倍率;数值越大滚轮滚动越快 | 2.5 | | crownSettleMs | 表冠/滚轮停止后等待多久结束滚动状态并做最终焦点校准 | 180 | | crownVibrate | 表冠实时中心条目发生切换时振动;新 API 为 20ms 短强,旧 API 为实测短中降级 | true | | debugScroll | 输出 [ZSList.scroll] 诊断日志 | false |

公开 API 不提供 x/y/w/h。列表始终从 x=0, y=0 开始,宽高由当前屏幕 profile 决定。若你选择 hideStatusBar: false 并需要避让状态栏,可以在列表顶部插入 SPACER

表冠振动使用能力检测,而不是硬编码系统版本:支持 API_LEVEL 3.6 场景 API 时,优先调用 getType() 并以 STRONG_SHORT 场景振动 20ms;接口缺失或调用失败时,回退到 API_LEVEL 2.0 的 setMode(VIBRATOR_SCENE_DURATION)start()。后者来自 Shimmer 在受影响旧固件上的实测映射,实际为短中振动,是有意的兼容降级。旧 mode 映射的具体修复版本尚未确认。

const list = createListPage({
  hideStatusBar: false,
  header: false,
})

list.createWidget(listWidget.SPACER, { h: 64 })

滚动倍率可直接在 createListPage() 中调整:

const list = createListPage({
  touchScrollStep: 1,  // 触屏滑动倍率,默认 1
  crownStep: 2.5,     // 表冠/滚轮倍率,默认 2.5
  crownVibrate: true, // 新系统短强、旧系统实测短中降级,默认开启
})

HeaderParam

| 参数 | 说明 | |---|---| | text | 页头标题 | | subtitle | 页头副标题;传入后使用复合标题布局 | | text_i18n | 页头标题 i18n 文本 | | subtitle_i18n | 页头副标题 i18n 文本 | | text_style | 标题/副标题统一文字布局样式 | | title_text_style | 标题文字布局样式,优先级高于 text_style | | subtitle_text_style | 副标题文字布局样式,优先级高于 text_style |

FooterParam

| 参数 | 说明 | 默认 | |---|---|---| | button | 是否显示居中的帮助按钮 | false | | click_func | 帮助按钮点击/确认回调 | noop |

Footer 高度会随 layout profile 变化:圆屏基准 188px,方屏基准 84px。默认 footer 不只是可选按钮区域,也承担底部安全留白,避免最后一屏内容被系统底部区域截断;没有帮助按钮需求时保留默认 footer: {} 即可。只有在你自行追加等效底部 SPACER 或完全自定义页面底部时,才建议传 footer: false

listWidget

| 值 | 说明 | |---|---| | listWidget.TEXT | 文本条目;可展示、可点击、可带左侧图标和右箭头 | | listWidget.SWITCH | 开关条目 | | listWidget.CHECKBOX | 复选框条目 | | listWidget.RADIO | 单选框条目 | | listWidget.CATEGORY | 分类说明文字 | | listWidget.IMAGE | 居中图片条目 | | listWidget.FOOTER | 手动插入 footer | | listWidget.SPACER | 手动插入垂直留白 |

TEXT / SWITCH / CHECKBOX / RADIO / IMAGE 返回 ListItemCATEGORY / FOOTER / SPACER 返回当前 ListPage,便于链式继续创建。

RowParam

适用于 TEXT / SWITCH / CHECKBOX / RADIO

| 参数 | 说明 | |---|---| | title | 主标题 | | subtitle | 副标题;传入后自动使用复合标题行 | | title_i18n | 主标题 i18n 文本 | | subtitle_i18n | 副标题 i18n 文本 | | text_style | 主/副标题统一文字布局样式,默认 text_style.WRAP | | title_text_style | 主标题文字布局样式,优先级高于 text_style | | subtitle_text_style | 副标题文字布局样式,优先级高于 text_style | | icon | 左侧业务图标路径;传入后显示 profile 缩放后的 64px 基准图标,并通过 auto_scale 等比缩放资源 | | disabled | 禁用交互,但仍保留在焦点序列中 | | clickable | TEXT 是否可点击;有 click_func 时自动视为可点击 | | click_func | TEXT 点击/确认回调 | | checked | SWITCH / CHECKBOX / RADIO 初始选中状态 | | checked_change_func | 状态变化回调 | | group | RADIO 单选组名 |

文本高度通过 ZeppOS 官方 getTextLayout() 计算。默认使用 text_style.WRAP,也支持官方 text_style.ELLIPSIS / text_style.NONE

文本条目

文本条目统一使用 listWidget.TEXT。是否显示右箭头、是否响应点击,由 clickable / click_func 决定。

list.createWidget(listWidget.TEXT, {
  title: '展示文本',
})

list.createWidget(listWidget.TEXT, {
  icon: 'app_icon.png',
  title: '进入详情',
  clickable: true,
  click_func: () => list.push({ url: 'page/detail' }),
})

规则:

  • clickable / click_func:进入焦点序列,但不显示焦点框、不响应点击或 SELECT/HOME
  • clickable: trueclick_func:显示右箭头,显示行焦点框,响应触屏和物理确认键
  • subtitle:自动使用双行/复合标题布局
  • 左侧 icon 和右侧箭头都会按当前 profile 尺寸自动等比缩放

开关条目 / Switch row

复选框条目 / Checkbox row

单选框条目 / Radio row

ImageParam

| 参数 | 说明 | 默认 | |---|---|---| | src | 图片资源路径 | 必填 | | w | 目标图片宽度;不传则读取资源宽度 | 图片原始宽度 | | h | 目标图片高度;不传则读取资源高度 | 图片原始高度 | | radius | 图片、按下遮罩、图片焦点框圆角 | imageFocusLine | | selectable | 是否显示图片双描边焦点框;不影响是否响应点击 | true | | click_func | 点击/确认回调 | none | | disabled | 禁用交互 | false |

图片规则:

  • x/y 由列表控制,传入会被忽略
  • 图片默认居中
  • 圆屏基准最大尺寸 408px
  • 方屏基准最大尺寸 326px,即 390 - 32 * 2
  • 超出最大尺寸时按比例缩放,最终 w/h 不会越过当前 profile 的 imageMax
  • 显式 radius 按图片最终缩放比例同步缩放,并同时用于 IMG.corner_radius、按下遮罩和双描边焦点框
  • 没有 click_func 的图片也会进入焦点序列,但不显示焦点框、不响应点击
  • click_funcselectable: false 的图片仍可点击,只是不显示图片焦点框
list.createWidget(listWidget.IMAGE, {
  src: 'banner.png',
  radius: 32,
  click_func: () => showToast({ content: '图片' }),
})

list.createWidget(listWidget.IMAGE, {
  src: 'display-only.png',
  selectable: false,
})

CategoryParam

| 参数 | 说明 | |---|---| | text | 分类文字 | | text_i18n | 分类文字 i18n 文本 | | text_style | 分类文字布局样式,默认 text_style.WRAP |

CATEGORY 会进入焦点序列,方便按键导航和滚动落焦保持节奏;它不显示焦点框,也不响应点击。

SPACER

SPACER 用于插入一段空白高度。

list.createWidget(listWidget.SPACER, { h: 32 })
list.createWidget(listWidget.SPACER, { height: 32 })

规则:

  • 不可点击
  • 不进入焦点序列
  • 不显示焦点框
  • 会增加 list.height
  • 常用于保留状态栏空间、分组间距或测试滚动边界

圆屏/方屏适配

ZSList 通过 @zos/device.getDeviceInfo().screenShape 自动选择布局 profile,并使用屏幕宽度做整体缩放。

需要权限:

{
  "permissions": ["data:os.device.info"]
}

基准 profile:

| 项 | 圆屏 | 方屏 | |---|---:|---:| | 设计宽度 | 480 | 390 | | 设计高度 | 480 | 450 | | 普通行高 | 104 | 96 | | 双行行高 | 124 | 122 | | 分类高度 | 72 | 72 | | footer 高度 | 188 | 84 | | 图片最大尺寸 | 408 | 326 | | 行焦点宽度 | 452 | 361 | | glow 图片 | 480×24 | 384×19 |

如果设备宽度不是基准宽度,layout、字体、控件、焦点、图片限制会按 deviceWidth / profileWidth 缩放。

页面跳转与浏览进度

从列表页跳转到其它页面时,建议使用实例方法 list.push(),不要直接调用 @zos/routerpush()

list.createWidget(listWidget.TEXT, {
  title: '进入详情页',
  clickable: true,
  click_func: () => list.push({ url: 'page/detail' }),
})

list.push() 会保存当前 scrollYfocusIndex。如果 ZeppOS 返回时重建原页面,mount() 会自动恢复并消费这条记录。

Modal 与覆盖层

不要在已经创建并 mount()ListPage 的同一 page 中直接调用 createModal()ListPage 会注册物理按键、触屏点击和焦点状态,modal 也是独立按键/覆盖层控件;二者在同一个 page 内同时存在时,容易出现按键事件或残留点击互相抢占。

推荐做法是:列表页只负责 list.push() 跳转,弹窗放到一个不创建 ListPage 的独立子页面中,并在子页面 build() 时创建并显示 modal;关闭、取消或确认后再 back() / exit()

getProperty / setProperty

支持常用非结构属性即时更新:

item.setProperty(listProp.TITLE, '新的标题')
item.setProperty(listProp.CHECKED, false)
list.setProperty(listProp.HEADER_TEXT, '设置')
list.setProperty(listProp.FOCUS_INDEX, 2)

const height = list.getProperty(listProp.HEIGHT)

| 值 | 说明 | 作用对象 | |---|---|---| | listProp.HEADER_TEXT | 页头标题 | list page | | listProp.FOCUS_INDEX | 当前焦点索引 | list page | | listProp.SCROLL_Y | 当前纵向滚动量 | list page | | listProp.HEIGHT | 列表内容总高度,也可读 list.height | list page | | listProp.TITLE | 条目主标题 | item | | listProp.SUBTITLE | 条目副标题 | item | | listProp.CHECKED | 条目选中状态 | switch/checkbox/radio item | | listProp.CLICK_FUNC | 点击回调 | item | | listProp.CHECKED_CHANGE_FUNC | 状态变化回调 | switch/checkbox/radio item | | listProp.DISABLED | 禁用交互 | item |

不支持即时重排:插入/删除行、修改行类型、单行和双行互切、可点击文本变不可点击后移除箭头等结构变化。

行为规则

  • 同一 page 建议只创建一个 ListPage;新实例会自动清理旧实例
  • mount() 后自动注册物理按键
  • UP/DOWN 环绕导航
  • SELECT/HOME 触发当前焦点项
  • SELECT/HOME 长按超过 1 秒取消触发
  • 触屏点按显示黑色按下遮罩
  • 按键触发不显示按下遮罩
  • 触屏按下后只要发生实际移动就取消本次点击,避免滚动时误触发
  • 手指滑动时隐藏焦点,松手并完成滚动后才落到屏幕中心最近项;表冠/滚轮滚动时保持焦点可见并实时切换中心项
  • display-only TEXT / IMAGE / CATEGORY 进入焦点序列,但不显示焦点框、不触发动作
  • header: false 不创建 header
  • footer: false 不创建 footer;这会移除默认底部安全留白,通常只适合已自行追加底部 SPACER 或自定义底部区域的页面
  • hideStatusBar 默认隐藏状态栏
  • Touch tap is cancelled on any actual pointer movement to avoid accidental row activation while scrolling

资源

至少需要复制这些资源:

assets/default.r/app_icon.png
assets/default.r/arrow.png
assets/default.r/qa.png
assets/default.r/focus_glow_top-1.png
assets/default.r/focus_glow_bot-1.png
assets/default.r/checkbox_on.png
assets/default.r/checkbox_off.png
assets/default.r/radio_on.png
assets/default.r/radio_off.png
assets/default.r/switch_on.png
assets/default.r/switch_off.png
assets/default.r/switch_knob.png

如果 app.json 里包含方屏平台,例如 { "st": "s", "dw": 390 },也需要提供对应的 assets/default.s/

Example / 示例

example/ 是完整 ZeppOS 3.0 小程序示例,覆盖圆屏、方屏、换行文本、图片、footer、嵌套跳转和状态恢复。

cd example
zeus build

English

What

Zepp-Style-List (ZSList) is a community-maintained ZeppOS system-style list component for mini-program pages that need native-looking rows, physical-key focus, touch feedback, crown/wheel scrolling, and round/square screen adaptation. It wraps common list page building blocks into one page-level component:

  1. ZeppOS system-style rows: TEXT, SWITCH, CHECKBOX, RADIO, CATEGORY, FOOTER, IMAGE
  2. Physical-key focus navigation
  3. Touch press feedback
  4. Deferred touch-scroll focus landing and real-time crown/wheel focus switching
  5. Automatic round/square screen adaptation

It is not a built-in ZeppOS widget, but the API intentionally resembles createWidget(widget.X, Param).

Install

npm install @zh40s05/zepp-style-list
cp zslist.js your-app/utils/zslist.js

Copy runtime assets into assets/default.r/; copy them into assets/default.s/ as well when building for square screens.

Quick Start

import { showToast } from '@zos/interaction'
import { createListPage, listWidget } from '../utils/zslist'

Page({
  build() {
    const list = createListPage({
      header: { text: 'Settings' },
      footer: { button: true, click_func: () => showToast({ content: 'Help' }) },
    })

    list.createWidget(listWidget.TEXT, {
      title: 'Clickable row',
      clickable: true,
      click_func: () => showToast({ content: 'click' }),
    })

    list.createWidget(listWidget.SWITCH, {
      title: 'Switch row',
      checked: true,
      checked_change_func: (checked) => showToast({ content: checked ? 'on' : 'off' }),
    })

    list.mount()
    this.list = list
  },
})

API

| Export | Description | |---|---| | createListPage(param) | Create a list page control | | listWidget | Widget type constants | | listProp | Property constants for getProperty / setProperty | | ListPage | List page class | | zslistDispose() | Manually dispose the current list instance |

createListPage Options

| Option | Description | Default | |---|---|---| | header | Header config; pass false to disable header | disabled | | footer | Footer config; pass false to disable footer | {} | | hideStatusBar | Calls setStatusBarVisible(false) on creation; pass false to keep current status-bar visibility | true | | touchScrollStep | Multiplier from touch scroll_frame_func.yoffset to internal scroll position; tune touch-scroll focus landing | 1 | | crownEnable | Register onDigitalCrown for crown/wheel scrolling | true | | crownStep | Multiplier from crown/wheel degree to scroll pixels; larger values scroll faster | 2.5 | | crownSettleMs | Delay before ending crown/wheel scrolling state and running final focus calibration | 180 | | crownVibrate | Vibrate on each real-time centered-item change from crown input; 20ms strong-short on the scene API, tested short-medium fallback on the legacy API | true | | debugScroll | Print [ZSList.scroll] diagnostic logs | false |

The public API does not expose x/y/w/h. The list always starts at x=0, y=0 and uses the active layout profile size. If you keep the status bar visible, insert a top SPACER when needed.

Crown haptics use capability detection instead of a hard-coded OS version. When the API_LEVEL 3.6 scene API is available, ZSList uses getType() and a 20ms STRONG_SHORT action. If that API is missing or fails, it falls back to the API_LEVEL 2.0 setMode(VIBRATOR_SCENE_DURATION)start() sequence. Shimmer true-device testing found that this legacy mode maps to a short-medium pulse on affected old firmware, so the fallback is an intentional downgrade. The exact firmware version that fixed the legacy mode mapping is unknown.

Keep the default footer (footer: {}) even when you do not need a visible help button. The footer also provides bottom safe spacing so the last screen of content is not clipped by the system bottom area. Use footer: false only when you add equivalent bottom spacing yourself or fully own the page bottom.

Modal and overlays

Do not call createModal() directly from a page that already created and mounted a ListPage. ListPage registers physical keys, touch handling, and focus state, while modal is its own key-consuming overlay. Keeping both active in the same page can make key events or residual clicks compete.

Recommended pattern: keep the list page responsible only for list.push() navigation, put the modal in a separate child page that does not create a ListPage, and create/show the modal in that child page's build(). Close, cancel, or confirm should then call back() / exit() as appropriate.

Tune scroll multipliers in createListPage():

const list = createListPage({
  touchScrollStep: 1,  // touch drag multiplier, default 1
  crownStep: 2.5,     // crown/wheel multiplier, default 2.5
  crownVibrate: true, // strong-short on new systems, tested legacy downgrade on old systems
})

Widgets

| Widget | Description | |---|---| | TEXT | Text row, optionally clickable, with optional left icon and arrow | | SWITCH | Switch row | | CHECKBOX | Checkbox row | | RADIO | Radio row | | CATEGORY | Section label | | IMAGE | Centered image row | | FOOTER | Manual footer | | SPACER | Manual vertical blank space |

Display-only TEXT, IMAGE, and CATEGORY entries are still part of the focus sequence for key navigation and scroll-center landing, but they do not show a focus frame and do not respond to click/SELECT.

Screen Adaptation

ZSList reads getDeviceInfo().screenShape and selects a round or square layout profile. It then scales layout, font, focus, footer controls, and image limits by screen width.

Requires "data:os.device.info" in app.json on runtimes that enforce device info permissions.

Baseline values:

| Item | Round | Square | |---|---:|---:| | Width | 480 | 390 | | Height | 480 | 450 | | Single row | 104 | 96 | | Multi row | 124 | 122 | | Footer | 188 | 84 | | Image max | 408 | 326 | | Focus width | 452 | 361 |

Images

Images are centered and capped by the active profile: round baseline max 408, square baseline max 326. Explicit radius is interpreted in the source/target image coordinate system and scales with the final image fit; the same result is used for the IMG.corner_radius, press mask, and image focus frame.

selectable: false suppresses the image focus frame only. If click_func exists, the image still responds to touch and SELECT/HOME.

Example

example/ is a complete ZeppOS 3.0 sample app.

cd example
zeus build