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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@hyext/popup

v2.8.0

Published

A popup miniapp appearance resolution.

Downloads

106

Readme

@hyext/popup

虎牙浮窗类型小程序布局展示方案

Installation

使用前,请先向[email protected]发送邮件(内容请带上extUuid)申请浮窗权限。

$ npm i @hyext/popup

Usage

Configuration

首先我们需要在项目根目录的project.config.json文件的compileNodeModules加上@hyext/popup字段。

{
  "builder": {
    "name": "@hyext/builder-beyond",
    "config": {
      "compileNodeModules": [
        "@hyext/popup"
      ]
    }
  }
}

Create PopupLayout

首先我们需要根据我们的业务需求, 创建一个 PopupLayout,步骤如下:

import { withPopupLayout, LayoutSwitch } from '@hyext/popup'

const WidgetMeasureBox = withPopupLayout(
  rootLayoutInfo => {
    const isLandscape = rootLayoutInfo.isLandscape

    // 例如我这个小挂件在公屏的左下角,宽高不指定,使用业务容器宽高。
    return {
      right: isLandscape ? 68 : 18, // 在横屏的时候距离右方68,竖屏的时候是18。
      top: isLandscape ? 'center' : '68%' // 在横屏的时候垂直方向居中,竖屏的时候距离底部68%的距离。
    }
  },
  {
    defaultWidth: 95, // 为了和内部容器的hycss对齐,这里会做一层转换,以750宽度的设计稿为例,按1:1的比例widget的宽高。
    defaultHeight: 95,
    id: 'widget',
    border: true // 调试用的, prod可以设置为false。
  }
)

const FullScreenMeasureBox = withPopupLayout(
  rootLayoutInfo => {
    // 例如我这个内部组件需要占满全屏

    return {
      left: 0,
      top: 0,
      width: '100%',
      height: '100%'
    }
  },
  {
    defaultWidth: 0, // 由于你已经把width & height设置为‘100%’, 内部容器的宽高会与rootLayoutInfo的大小一致, 所以这里设置不设置宽高都无所谓。
    defaultHeight: 0,
    id: 'fullScreen',
    border: true // 调试用的。prod可以设置为false
  }
)

Declarative

对应声明式的代码风格,我们可以接着上下文这样使用它们:

// 连接上下文 WidgetMeasureBox FullScreenMeasureBox
const widgetLayoutName = 'widget-layout'
const fullScreenLayoutName = 'full-screen-layout'

function App() {
  const [layoutName, setLayoutName] = useState(widgetLayoutName)

  return (
    <>
      <Button onPress={() => setLayoutName(fullScreenLayoutName)}>
        切换全屏
      </Button>
      <LayoutSwitch currentLayoutName={layoutName}>
        <WidgetMeasureBox name={widgetLayoutName}>
          <MyWidgetComponent></MyWidgetComponent>
        </WidgetMeasureBox>
        <FullScreenMeasureBox
          name={fullScreenLayoutName}
        > 
          <MyFullScreentComponent></MyFullScreentComponent>
        </FullScreenMeasureBox>
      </LayoutSwitch>
    </>
  )
}

note: LayoutSwitch只能用currentLayoutName。

Programmatic

对应编程式的代码风格,我们可以接着上下文这样使用它们:

// 连接上下文 WidgetMeasureBox FullScreenMeasureBox
const widgetLayoutName = 'widget-layout'
const fullScreenLayoutName = 'full-screen-layout'

function App() {
  return (
    <>
      <Button onPress={() => setLayoutName(fullScreenLayoutName)}>
        切换全屏
      </Button>
      <LayoutSwitch defaultLayout={layoutName}>
        <WidgetMeasureBox name={widgetLayoutName}>
          <MyWidgetComponent></MyWidgetComponent>
        </WidgetMeasureBox>
        <FullScreenMeasureBox
          name={fullScreenLayoutName}
        > 
          <MyFullScreentComponent></MyFullScreentComponent>
        </FullScreenMeasureBox>
      </LayoutSwitch>
    </>
  )
}

// 重点看这部分
function MyWidgetComponent(props) {
  const layout = props.layout

  return (
    <Button onPress={() => layout.replace(fullScreenLayoutName)}>切换回全屏</Button>
  )
}

function MyFullScreentComponent(props) {
  const layout = props.layout

  return (
    <Button onPress={() => layout.replace(widgetLayoutName)}>切换回小挂件</Button>
  )
}

note: LayoutSwitch只能用defaultLayout。

Compoments

LayoutSwitch

浮窗 UI 展示控件

Props

| Name | Type | Required | Default | Description | | ----------------- | ------------------ | -------- | ------- | ---------------------------------------- | | defaultLayout | string/null | false | void | 默认 layout 的名字,使用编程式风格时存在 | | currentLayoutName | string/null | false | void | 当前 layout 名字,使用声明式风格时存在 | | children | React.ReactElement | true | void | 对应的 PopupLayout 组件 |

PopupLayout

一个 HOC 组件,当发生任何场景切换的时候(不限于横竖屏切换,业务层切换等等),负责计算浮窗位置和大小。

Props

| Name | Type | Required | Default | Description | | ---- | ------ | -------- | ------- | ---------------- | | name | string | true | void | 该 layout 的名字 |

withPopupLayout

withPopupLayout(options, hocOptions)会创建一个PopupLayout组件。

options 参数说明

| Name | Type | Required | Default | Description | | ---------------------- | ------------------------------------- | -------- | ------- | -------------------- | | WithPopupLayoutOptions | UserLayoutOptions/UserLayoutOptionsFN | true | void | withPopupLayout 选项 |

WithPopupLayoutOptions可以是一个对象 UserLayoutOptions, 含有 2 种类型属性,第一种是位置属性,另一种是大小属性

位置属性包含:topbottomleftright, 有 4 种组合:top & lefttop & rightbottom & leftbottom & right,它们都可以接受number or 百分比 or center三种值。

大小属性包含:widthheight, 它们是非必填的,可以接受number or 百分比,在不填的时候,会使用内部容器的宽高作为根容器的宽高。

WithPopupLayoutOptions亦可以是一个函数:(layout:OnLayoutChangeCallbackRes) => UserLayoutOptions,OnLayoutChangeCallbackRes 数据结构,在 Hooks 环节中有所提及,函数返回的是UserLayoutOptions

hocOptions 参数说明

| Name | Type | Required | Default | Description | | ------------- | ------- | -------- | ------- | ------------------------------------- | | defaultWidth | number | true | void | 默认业务容器的宽 | | defaultHeight | number | true | void | 默认业务容器的高 | | id | string | false | void | HOC 的 displayName | | border | boolean | false | void | HOC 内部容器是否加边框,用来 debug 的 |

PopupLayout 会向它的子组件传入一个layout对象,方便子组件可以使用编程式的风格操作 layout,访问方式:props.layout,其接口如下:

  • layout.replace(v: string): Promise<void> - 切换其他 layout

  • layout.show(): Promise<void> - 展示本 layout

  • layout.hide(): Promise<void> - 隐藏本 layout

其中 layout.show()layout.hide()是对于自身组件来说的展示和隐藏,只针对已渲染组件。

Hooks

useLayoutChange(deps: any[], cb: UserLayoutOptionsFN): ChangedResult

由于LayoutSwitch的渲染方案粒度太粗,一更新就整个页面更新,而使用useLayoutChangehook返回的状态去渲染UI会更加灵活,例如用在营收浮窗面板,此方案更加。

demo:

// 不建议在函数内部改变 react 的 state, state 可通过 hook 返回的结果改变
function renderLayout(res: OnLayoutChangeCallbackRes) {
 const { isLandscape } = res
  if (isLandscape) {
    return {
      layout: 'landscape',
      left: 'center',
      top: 'center',
      width: '50%',
      height: 100
    }
  } else {
    return {
      layout: 'not-landscape',
      left: '10%',
      top: '5%',
      width: '50%',
      height: 100
    }
  }
}

function Demo() {
  // deps is useEffect deps
  const { layout, status, error, isLandscape } = useLayoutChange(deps, renderLayout)

  // 可以利用临界状态 做一些事情 让UI切换更加平滑
  if (status === 'none' || status === 'updating') return null

  if (status === 'error') {
    return <Abnor type="error" desc={error.message} />
  }
  
  // 可以根据用户定义的 layout 名字,映射不同的UI
  switch (layout) {
    case: 'not-landscape'
      return <NotLandscapeUI>
    case: 'landscape'
      return <LandscapeUI>
    default: 
      return null
  }
}

参数说明

export type OnLayoutChangeCallbackRes = {
  isLandscape: boolean
  screenHeight: number
  screenWidth: number
}

export type UserLayoutOptions = {
  layout?: string
  left?: number | string
  top?: number | string
  right?: number | string
  bottom?: number | string
  width?: number | string
  height?: number | string
  alpha?: number
  visible?: boolean
}

export type UserLayoutOptionsFN = (
  layout: OnLayoutChangeCallbackRes
) => UserLayoutOptions | Promise<UserLayoutOptions>


export type ChangeResult = {
 status: LayoutStatus
 layout?: string
 error: null | Error
 setedLayoutOptions: SetLayoutOptions
} & OnLayoutChangeCallbackRes

type LayoutStatus = 'none' | 'updating' | 'updated' | 'error'

type SetLayoutOptions = typeof hyext.panel.setLayout

useLayoutChangeWithQueue(params: UseLayoutChangeWithQueueParams): ChangedResult

和 useLayoutChange 类似, 只是加强了 NOTICE 模式的支持, 同时修改了部分API:

  1. 当为NOTICE时, 仅有当onNoticeShow触发后 status 才会变为 updated
  2. 获取setLayoutOptions的函数, 入参增加了 error, errorOpts, expUI.
    • error: 初始值为null, 当发生错误时, 会被赋值为错误对象. 可以使用这个对象来达到fallback的效果. 在同一次onLayoutChange中, 最多只会尝试3次, 仍然失败后 status 变为 error
    • errorOpts: 出错时的配置
    • expUI: 是否应当显示实验组UI
  3. useLayoutChangeWithQueue 的参数改成了对象, 同时增加log参数
  4. useLayoutChangeWithQueue 的返回值增加了 extUI 和 onShowPayload
function renderLayout(info: GetLayoutOptsParams) {
 const { isLandscape, error, errorOpts, expUI } = info
  if (isLandscape) {
    return {
      layout: 'landscape',
      left: 'center',
      top: 'center',
      width: '50%',
      height: 100
    }
  } else {
    // 首先尝试使用NOTICE模式
    if (!error) {
      return {
        mode: 'NOTICE',
        liveroomPopupKey: 'superfans_autotrigger',
      }
    } else {
            // 失败则使用setLayout
      return {
        layout: 'not-landscape',
        left: '10%',
        top: '5%',
        width: '50%',
        height: 100
      }
    }

  }
}
function Demo() {
  // deps is useEffect deps
  const { layout, status, error, isLandscape, expUI } = useLayoutChangeWithQueue({deps, getLayoutOpts: renderLayout, log: console.log})

  // 可以利用临界状态 做一些事情 让UI切换更加平滑
  if (status === 'none' || status === 'updating') return null

  if (status === 'error') {
    return <Abnor type="error" desc={error.message} />
  }
  
  // 可以根据用户定义的 layout 名字,映射不同的UI
  switch (layout) {
    case: 'not-landscape'
      return <NotLandscapeUI>
    case: 'landscape'
      return <LandscapeUI>
    default: 
      return null
  }
}

参数说明

/* interface UserLayoutOptions, 除了可以透传 setMode 的参数, 还可以透传 setModeWithQueue 的参数, 如
 * onShow/onNoticeHide/showTime/waitAfterResetNormal/skipResetNormal
 * 详见 https://git.huya.com/exc/web/hyext-miniapp/-/tree/master/packages/hyext-utils#getsetmodewithqueuefn
 */
type GetLayoutOptsParams = OnLayoutChangeCallbackRes & { expUI: boolean, retryCount: number, errorOpts?: UserLayoutOptions, error?: Error }

interface UseLayoutChangeWithQueueParams {
  getLayoutOpts: (params: GetLayoutOptsParams) => UserLayoutOptions | Promise<UserLayoutOptions>,
  deps?: any[],

  /* 新增入参 */
  log?: (msg: string) => void
}

type ChangedResult = {
  status: LayoutStatus

  // 以下数据在 status 不等于 ‘none’ 时存在
  error: Error | null
  layout?: string

  isLandscape?: boolean
  screenHeight?: number
  screenWidth?: number

  setedLayoutOptions: {
    visible: boolean
    x: number
    y: number
    alpha: number
    width: number
    height: number
  } | null

  /* 新增数据 */
  expUI: boolean // 是否展示实验组UI(当前用户属于实验组)
  onShowPayload: {} // onNoticeShow 触发时携带的数据, 也可以在 getLayoutOpts 函数中透传setModeWithQueue 的onShow参数
  onHidePayload: {} // onNoticeHide 触发时携带的数据, 也可以在 getLayoutOpts 函数中透传setModeWithQueue 的onNoticeHide参数
}