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

@d-matrix/utils

v1.23.0

Published

A dozen of utils for Front-End Development

Downloads

87

Readme

@d-matrix/utils

NPM Downloads npm bundle size

A dozen of utils for Front-End Development

API

clipboard

  • writeImage(element: HTMLImageElement | null | string): Promise<void>

复制图片到剪贴板

  • writeText(text: string): Promise<void>

复制文本到剪切板

react

  • render<P>(element: ReactElement<P>): Promise<string>

渲染React组件,返回HTML字符串。

  • cleanup(): void

清理函数,需要在调用render()函数后调用。

  • useDisableContextMenu(target: ContextMenuTarget = defaultContextMenuTarget): void

target函数返回的元素上禁用右键菜单,默认的target() => document

例1:在idtest的元素上禁用右键菜单

import { react } from '@d-matrix/utils';

const TestComp = () => {
  react.useDisableContextMenu(() => document.getElementById('test'));

  return (
    <div>
      <div id='test'>此元素的右键菜单被禁用</div>
    </div>
  )
}

例2:在document上禁用右键菜单

const TestComp = () => {
  react.useDisableContextMenu();

  return <div>内容</div>
}
  • useStateCallback<T>(initialState: T): [T, (state: T, cb?: (state: T) => void) => void]

返回值setState()函数类似类组件中的setState(updater[, callback]),可以在callback中获取更新后的state

  • useIsMounted(): () => boolean

获取当前组件是否已挂载的 Hook

const Test = () => {
  const isMounted = useIsMounted();

  useEffect(() => {
      if (isMounted()) {
       console.log('component mounted')
      }
  }, [isMounted]);

  return null
};
  • useCopyToClipboard(props?: UseCopyToClipboardProps)

复制文本到剪切板, 用法见测试

  • EnhancedComponent.prototype.setStateAsync(state)

setState()方法的同步版本

import { react } from '@d-matrix/utils';

 class TestComponent extends EnhancedComponent<unknown, { pageIndex: number }> {
  state = {
    pageIndex: 1,
  };

  async onClick() {
    await this.setStateAsync({ pageIndex: 2 });
    console.log(this.state.pageIndex); // 2
  }

  render() {
    return (
      <button data-cy="test-button" onClick={() => this.onClick()}>
        click
      </button>
    );
  }
}
  • useDeepCompareRef(deps: DependencyList): React.MutableRefObject<number>

深比较deps。返回refref.current是一个自增数字,每次deps变化,ref.current1。用法见测试

  • InferRef<T>

推导子组件的ref类型,适用于组件没有导出其ref类型的场景, 更多用法见测试

interface ChildRefProps {
  prop1: () => void;
  prop2: () => void;
}

interface ChildProps {
  otherProp: string;
}

const Child = React.forwardRef<ChildRefProps, ChildProps>((props, ref) => {
  React.useImperativeHandle(
    ref,
    () => ({
      prop1() {},
      prop2() {},
    }),
    [],
  );

  return null;
});

type InferredChildRef = InferRef<typeof Child>;  // 等价于ChildRefProps

const Parent = () => {
  const childRef = React.useRef<InferredChildRef>(null);

  return <Child ref={childRef} otherProp="a" />;
};
  • useForwardRef = <T>(ref: ForwardedRef<T>, initialValue: any = null): React.MutableRefObject<T>

解决使用React.forwardRef后,在调用ref.current.someMethod()时, 出现Property 'current' does not exist on type '(instance: HTMLInputElement | null) => void' TS类型错误,具体问题见这里

const Input = React.forwardRef<HTMLInputElement, React.ComponentPropsWithRef<'input'>>((props, ref) => {
  const forwardRef = useForwardRef<HTMLInputElement>(ref);
  useEffect(() => {
    forwardRef.current.focus();
  });
  return <input type="text" ref={forwardRef} value={props.value} />;
});
  • useMediaQuery(query, options?): boolean

使用Match Media API 检测当前document是否匹配media query

import { useMediaQuery } from '@d-matrix/utils/react'

export default function Component() {
  const matches = useMediaQuery('(min-width: 768px)')

  return (
    <div>
      {`The view port is ${matches ? 'at least' : 'less than'} 768 pixels wide`}
    </div>
  )
}

dom

  • scrollToTop(element: Element | null | undefined): void

元素滚动条滚动到顶部,对老旧浏览器做了兼容,见浏览器兼容性

  • strip(html: string): string

从字符串中去除 HTML 标签并返回纯文本内容。

import { dom } from '@d-matrix/utils';

dom.strip('测试<em>高亮</em>测试'); // '测试高亮测试'

date

  • rangeOfYears(start: number, end: number = new Date().getFullYear()): number[]

创建startend之间的年份数组。

  • getYears()
export interface YearOption {
  label: string;
  value: number;
}

export enum YearOptionKind {
  Numbers,
  Objects,
}

export type GetYearsOptions = {
  // 开始年份
  startYear?: number;
  // 最近几年
  recentYears?: number;
  // 截止年份
  endYear?: number;
  // 后缀,默认为'年'
  suffix?: string;
};

export function getYears(options: GetYearsOptions & { type: YearOptionKind.Numbers }): number[];
export function getYears(options: GetYearsOptions & { type: YearOptionKind.Objects }): YearOption[];
export function getYears(options: GetYearsOptions & { type: YearOptionKind }): number[] | YearOption[]

获取n年,typeYearOptionKind.Numbers,返回[2023, 2022, 2021]数字数组;typeYearOptionKind.Objects,返回如下的对象数组

[
  { value: 2023, label: '2023年' },
  { value: 2022, label: '2022年' },
  { value: 2021, label: '2021年' },
]

更多用法,见测试用例

  • dayOfWeek(num: number, lang: keyof typeof i18n = 'zh'): string

返回星期几, lang仅支持zhen, num必须为正整数,否则报错

dayOfWeek(0) // "日"

types

  • WithOptional<T, K extends keyof T>
type A = { a: number; b: number; c: number; };
type T0 = WithOptional<A, 'b' | 'c'>;  // { a: number; b?: number; c?: number }
  • FunctionPropertyNames<T>

获取对象中的方法名称,返回union type

class A {
  add() {}
  minus() {}
  div() {}
  public result: number = 0;
}
type T0 = FunctionPropertyNames<A>; // 'add' | 'minus' | 'div'

const t1 = {
  add() {},
  minus() {},
  div() {},
  result: 0,
};
type T1 =  FunctionPropertyNames<typeof t1>; // 'add' | 'minus' | 'div'
  • NonFunctionPropertyNames<T>

获取对象中非函数属性名称,返回union type

class A {
  add() {}
  minus() {}
  div() {}
  public result: number = 0;
}
type T0 = FunctionPropertyNames<A>; // 'result'

const t1 = {
  add() {},
  minus() {},
  div() {},
  result: 0,
};
type T1 =  FunctionPropertyNames<typeof t1>; // 'result'
  • ValueOf<T>

获取对象中key的值,返回由这些值组成的union type

const map = {
  0: '0m',
  1: '1m',
  2: '2m',
  3: '3m',
  4: '4m',
  5: '5m',
  6: '6m',
} as const;

type T0 = ValueOf<typeof map>; // '0m' | '1m' | '2m' | '3m' | '4m' | '5m' | '6m'
  • WithRequired<T, K extends keyof T>

指定属性变为必选

type Input = {
  a: number;
  b?: string;
};
type Output = WithRequired<Input, 'b'> // { a: number; b: string }

algorithm

  • function nodeCountAtDepth(root: Record<string, any>, depth: number, childrenKey: string = 'children'): number;

计算指定层级的节点数量

const root = {
  id: 1,
  children: [
    { id: 2, children: [{ id: 21 }, { id: 22 }, { id: 23 }] },
    { id: 3, children: [{ id: 31 }, { id: 32 }, { id: 33 }] },
  ],
};
expect(tree.nodeCountAtDepth(root, 0)).to.be.equal(1);
expect(tree.nodeCountAtDepth(root, 1)).to.be.equal(2);
expect(tree.nodeCountAtDepth(root, 2)).to.be.equal(6);
  • const findNode = <T extends Record<string, any>>(tree: T[], predicate: (node: T) => boolean, childrenKey = 'children'): T | null

找到符合条件的节点

const root = {
  id: 1,
  children: [
    { id: 2, children: [{ id: 21 }, { id: 22 }, { id: 23 }] },
    { id: 3, children: [{ id: 31 }, { id: 32 }, { id: 33 }] },
  ],
};
const actual = tree.findNode([root], (node) => node.id === 3);
expect(actual).to.be.deep.equal(root.children[1]);

const actual2 = tree.findNode([root], (node) => node.id === 33);
expect(actual2).to.be.deep.equal(root.children[1].children[2]);

file

  • toImage(file: BlobPart | FileURL, options?: BlobPropertyBag): Promise<HTMLImageElement>

转换BlobPart或者文件地址为图片对象

  • validateImageSize(file: BlobPart | FileURL, limitSize: { width: number; height: number }, options?: BlobPropertyBag): Promise<ImageSizeValidationResult>

返回值:

interface ImageSizeValidationResult {
  isOk: boolean;
  width: number;
  height: number;
}

图片宽,高校验

  • isImageExists(src: string, img: HTMLImageElement = new Image()): Promise<boolean>

检测图片地址是否可用

import { file } from '@d-matrix/utils';

const url = 'https://picsum.photos/200/300';
const res = await file.isImageExists(url);

传入HTML中已经存在的img元素

import { file } from '@d-matrix/utils';

const $img = document.getElementById('img');
const res = await file.isImageExists(url, $img);
  • getFilenameFromContentDispositionHeader(header: { ['content-disposition']: string }): string

Content-Disposition response header中获取filename

import { file } from '@d-matrix/utils';

const header = {
  'content-disposition': 'attachment;filename=%E5%A4%A7%E8%A1%8C%E6%8C%87%E5%AF%BC2024-06-27-2024-06-28.xlsx'
};
const filename = file.getFilenameFromContentDispositionHeader(header);
// '大行指导2024-06-27-2024-06-28.xlsx'
  • download(source: string | Blob, fileName = '', target?: HyperLinkTarget): void

文件下载,source是文件地址或blob对象。

type HyperLinkTarget = "_self" | "_blank" | "_parent" | "_top"
  • downloadFileByIframe(source: string): boolean

通过创建iframe进行文件下载

support

  • isBrowserEnv(): boolean

是否是浏览器环境

  • isWebSocket(): boolean

是否支持WebSocket

  • isSharedWorker(): boolean

是否支持SharedWorker

timer

  • sleep(ms?: number): Promise<unknown>

使用setTimeoutPromise实现,暂停执行ms毫秒

await sleep(3000); // 暂停3秒
console.log('continue'); // 继续执行

operator

  • trueTypeOf = (obj: unknown): string

检查数据类型

trueTypeOf([]); // array
trueTypeOf({}); // object
trueTypeOf(''); // string
trueTypeOf(new Date()); // date
trueTypeOf(1); // number
trueTypeOf(function () {}); // function
trueTypeOf(/test/i); // regexp
trueTypeOf(true); // boolean
trueTypeOf(null); // null
trueTypeOf(undefined); // undefined

decimal

  • format(value: number | string | undefined | null, options?: FormatOptions): string

格式化数字,默认保留3位小数,可添加前缀,后缀,默认值为'--',用法见测试

type FormatOptions = {
  decimalPlaces?: number | false;
  suffix?: string;
  prefix?: string;
  defaultValue?: string;
  operation?: {
    operator: 'add' | 'sub' | 'mul' | 'div' | 'toDecimalPlaces';
    value: number;
  }[];
};

object

  • removeZeroValueKeys = <T extends Record<string, any>>(obj: T, zeroValues = ZeroValues): T

移除零值的键, 默认的零值是:undefinednull, '', NaN, [], {}

removeZeroValueKeys({ a: '', b: 'abc', c: undefined, d: null, e: NaN, f: -1, g: [], h: {} })
// { b: 'abc', f: -1 }
  • typedKeys(obj: T): Array<keyof T>

返回tuple,而不是string[]

const obj = { a: 1, b: '2' };
Object.keys(obj) //  string[]
object.typedKeys({ a: 1, b: '2' }) // ('a' | 'b')[]

array

  • moveImmutable<T>(array: T[], fromIndex: number, toIndex: number): T[]
import { array } from '@d-matrix/utils';

const input = ['a', 'b', 'c'];

const array1 = array.moveImmutable(input, 1, 2);
console.log(array1);
//=> ['a', 'c', 'b']

const array2 = array.moveImmutable(input, -1, 0);
console.log(array2);
//=> ['c', 'a', 'b']

const array3 = array.moveImmutable(input, -2, -3);
console.log(array3);
//=> ['b', 'a', 'c']
  • moveMutable<T>(array: T[], fromIndex: number, toIndex: number): void

  • moveToStart<T>(array: T[], predicate: (item: T) => boolean): T[]

移动元素到数组首位,不会修改原数组

import { array } from '@d-matrix/utils';

const list = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }];
const newList = array.moveToStart(list, (item) => item.id === 4);

// [{ id: 4 }, { id: 1 }, { id: 2 }, { id: 3 }, { id: 5 }]
  • moveMulti<T extends unknown>(arr: T[], indexes: number[], start: number): T[]

移动多个元素到数组中指定的位置,用法,见测试用例

number

  • randomInt(min: number, max: number): number

返回min, max之间的随机整数

echarts

  • mergeOption(defaults: EChartsOption, overrides: EChartsOption, option?: deepmerge.Options): EChartsOption

deep merge Echarts配置,用法见测试用例

  • fill<T extends Record<string, any>, XAxisField extends keyof T, YAxisField extends keyof T>(dataSource: T[], xAxisField:XAxisField, yAxisField: YAxisField): T[]

场景:后端接口返回某几个时间点的数据,需求是在接口数据的基础上每隔5分钟补一个点,以达到图中的效果: 折线图

填充的点的Y轴值为前一个点的值, 时间示例: [9:23, 9:27] => [9:23, 9:25, 9:27, 9:30],更多,见测试用例

  • calcYAxisRange<T extends Record<string, any>, Key extends keyof T>(data: T[], key: Key, decimalPlaces = 2, splitNumber = 5): { max:number; min:number }

计算echarts YAxis的max和min属性,以达到根据实际数据动态调整,使折线图的波动明显。且第一个点始终在Y轴中间位置,效果图

测试

运行全部组件测试

npm run cy:component:all

运行单个组件测试

npm run cy:component -- tests/date.cy.ts

运行E2E测试

src通过tsc build到public/dist目录

npm run build:public

启动一个Web服务器来访问public/index.html文件,dist目录的脚本可以通过<script type="module"/>引入

npm run serve

最后启动cypress GUI客户端,选择E2E测试

npm run cy:open

发布

更新package version:

npm version <minor> or <major>...

构建:

npm build

发布:

npm publish --access public

网络原因导致连接registry服务器超时,可指定proxy

npm --proxy http://127.0.0.1:7890 publish

镜像站查询版本与手动同步:

npm镜像站

通过git log命令获取changelogs,用于填写GitHub Release内容:

git log --oneline --decorate

注意事项