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

@qfei-design/make-filter

v0.2.7

Published

Headless Make advanced filter model, React panel, and optional host component adapters.

Readme

@qfei-design/make-filter

Make advanced filter package with a headless core, a host-controlled React panel, optional component-library adapters, and Make Data API compatible filter.expression output.

The v1 interaction and internal panel styling follow the current ExpensePoc advanced-filter baseline. The package does not render Popover, Modal, Drawer, or any scroll container; host apps decide where to mount the panel and how wide, tall, or scrollable that container is. It also does not implement table-header filter UI. Header filter UI is a host integration that may call the package controller.

For Make App record-list pages, filtering is normally delivered as one integrated feature. Make App filtering should wire the toolbar advanced filter and host-owned CanvasTable header linkage together: this package provides the advanced filter model, panel, controller, and openWithField; the host provides the CanvasTable header UI/menu and calls the same controller.

Install

pnpm add @qfei-design/make-filter@^0.2.5
npm install @qfei-design/make-filter@^0.2.5
yarn add @qfei-design/make-filter@^0.2.5

0.2.5 is the minimum runtime baseline for Lookup filtering with source-field CEL expressions. The package publishes this README, PUBLIC_API.md, and the root-level machine-readable integration metadata.

Public Entrypoints

  • @qfei-design/make-filter: headless core helpers, operators, validation, summary, CEL compile/parse, and compileListFilter.
  • @qfei-design/make-filter/react: AdvancedFilterPanel, useAdvancedFilterController, candidate-source types, and component contracts.
  • @qfei-design/make-filter/adapters/antd: createAntdFilterComponents for Ant Design hosts.
  • @qfei-design/make-filter/styles.css: internal panel styles. Import once in the host UI entry.

Do not import from src, dist, or package-internal files.

Quick Start: AntD Popover Host

The package renders only the advanced filter panel. The host owns the toolbar button, Popover, width, max height, scroll behavior, applied state, and record reload.

import { Button, Popover } from "antd";
import { useState } from "react";
import {
  AdvancedFilterPanel,
  useAdvancedFilterController,
} from "@qfei-design/make-filter/react";
import { createAntdFilterComponents } from "@qfei-design/make-filter/adapters/antd";
import "@qfei-design/make-filter/styles.css";

const filterComponents = createAntdFilterComponents();

export function AdvancedFilterPopover({
  appliedGroup,
  candidateSources,
  fields,
  onConfirm,
}) {
  const [open, setOpen] = useState(false);
  const controller = useAdvancedFilterController({
    fields,
    value: appliedGroup,
    onChange: onConfirm,
  });

  function handleOpenChange(nextOpen) {
    if (nextOpen) {
      controller.beginDraft();
      setOpen(true);
      return;
    }
    controller.resetDraft();
    setOpen(false);
  }

  function handleConfirm() {
    const validation = controller.confirm();
    if (validation.valid) setOpen(false);
  }

  return (
    <Popover
      open={open}
      trigger="click"
      placement="bottomLeft"
      content={
        <div style={{ width: 724, maxHeight: 560, overflow: "auto" }}>
          <AdvancedFilterPanel
            candidateSources={candidateSources}
            components={filterComponents}
            fields={fields}
            value={controller.draftValue}
            validationErrors={controller.validationErrors}
            onChange={controller.setDraftValue}
            onClear={controller.clearDraft}
            onConfirm={handleConfirm}
          />
        </div>
      }
      onOpenChange={handleOpenChange}
    >
      <Button>筛选</Button>
    </Popover>
  );
}

Quick Start: Compile Service Filter

Use compileListFilter to merge toolbar search and applied advanced filters.

import { compileListFilter } from "@qfei-design/make-filter";

const filter = compileListFilter({
  advancedFilter: appliedGroup,
  fields,
  searchText,
});

const requestPayload = filter ? { filter } : {};

When compileListFilter returns undefined, omit filter. Do not send filter: [], filter: {}, or { expression: "" }.

DateRange Filtering

Pass DateRange fields as normal Make field metadata:

{ key: "period", name: "报销周期", type: "Make.Field.DateRange" }

The package keeps the original field as a whole-range condition and also adds two generated field options in the panel:

  • 报销周期.开始时间 -> period['begin']
  • 报销周期.结束时间 -> period['end']

Whole DateRange empty checks compile to period == null and period != null. Whole DateRange equality keeps the backend range-boundary contract: period['begin'] == "2026-07-01" && period['end'] == "2026-07-31".

The generated start/end options use the single-date operator matrix and value editor. Hosts should not create these options manually or use period['begin'] / period['end'] as raw fieldKey values; the package owns the generated collision-safe keys and CEL rendering.

Package provides

  • Filter IR helpers.
  • Make field support and operator matrix.
  • Default values, validation, and active-condition summary.
  • CEL expression compile and parse.
  • AdvancedFilterPanel for React hosts.
  • Complete default condition rows with the first filterable field and first supported operator selected. Field dropdowns are not auto-opened by the package.
  • useAdvancedFilterController for draft, reset, clear, confirm, validation, and openWithField for host-owned field entry points.
  • candidateSources props for remote user and department values.
  • createAntdFilterComponents for Ant Design controls.
  • ExpensePoc-derived internal panel styling through styles.css.

Host app provides

  • Normalized Make field metadata.
  • Resolved Lookup relation and target field metadata when Lookup fields should be filterable.
  • Applied advanced-filter state.
  • Toolbar trigger placement.
  • Popover, Modal, Drawer, or another mounting container.
  • Container width, max height, and scrolling.
  • User and department candidate APIs.
  • Service request adapter and record reload timing.
  • CanvasTable header filter UI, menu behavior, and openWithField linkage.
  • Optional URL/deep-link encoding and parsing policy.

Lookup Host Schema Resolution

Lookup filtering requires the host to resolve the complete runtime schema before passing fields to the package. Do not read copied DSL files or infer the target type from record values. Start from the current Entity, its Lookup field, all runtime relations, and all runtime entities.

For each Make.Field.Lookup source field:

  1. Read relationKey and targetFieldKey from the source field or its properties.
  2. Find the relation and select the opposite Entity from from / to.
  3. Find targetFieldKey in that target Entity.
  4. Pass the source Lookup key plus resolved target field metadata to this package.

This reference resolver accepts normalized runtime schema objects using entityKey, fieldKey, fieldType, relationKey, from, and to:

const text = (value) =>
  typeof value === "string" ? value.trim() : "";

const normalizeFilterField = (field) => ({
  key: text(field.fieldKey ?? field.key),
  name: text(field.name) || text(field.fieldKey ?? field.key),
  type: text(field.fieldType ?? field.type),
  properties: field.properties ?? {},
  meta: field.meta ?? {},
  disabled: Boolean(field.disabled),
});

function resolveLookupFilterField(
  sourceField,
  sourceEntityKey,
  schema,
) {
  const normalized = normalizeFilterField(sourceField);
  if (normalized.type !== "Make.Field.Lookup") return normalized;

  const relationKey = text(
    sourceField.relationKey ?? sourceField.properties?.relationKey,
  );
  const targetFieldKey = text(
    sourceField.targetFieldKey ?? sourceField.properties?.targetFieldKey,
  );
  const relation = (schema.relations ?? []).find(
    (item) => text(item.relationKey ?? item.key) === relationKey,
  );
  const fromEntityKey = text(relation?.from?.entityKey);
  const toEntityKey = text(relation?.to?.entityKey);
  const targetEntityKey =
    fromEntityKey === sourceEntityKey
      ? toEntityKey
      : toEntityKey === sourceEntityKey
        ? fromEntityKey
        : "";
  const entities = schema.objects ?? schema.entities ?? [];
  const targetEntity = entities.find(
    (item) => text(item.entityKey ?? item.key) === targetEntityKey,
  );
  const targetField = targetEntity?.fields?.find(
    (item) => text(item.fieldKey ?? item.key) === targetFieldKey,
  );
  const targetFieldType = text(targetField?.fieldType ?? targetField?.type);

  if (
    !relationKey ||
    !targetFieldKey ||
    !targetField ||
    !targetFieldType ||
    targetFieldType === "Make.Field.Lookup"
  ) {
    return normalized;
  }

  return {
    ...normalized,
    lookup: {
      relationKey,
      targetField: {
        fieldKey: targetFieldKey,
        fieldType: targetFieldType,
        name: targetField.name,
        properties: targetField.properties,
        meta: targetField.meta,
        disabled: Boolean(targetField.disabled),
      },
    },
  };
}

const fields = currentEntity.fields.map((field) =>
  resolveLookupFilterField(field, currentEntity.entityKey, schema),
);

If the relation, target Entity, target field, or target type cannot be resolved, leave the Lookup field without lookup metadata. The package treats it as unsupported and hides it from field selectors and header filter actions.

Pass the same resolved fields array to the panel, compiler, validation, search, and parser:

import {
  compileListFilter,
  parseCelToAdvancedFilter,
} from "@qfei-design/make-filter";

const filter = compileListFilter({
  advancedFilter: appliedGroup,
  fields,
  searchText,
});
const requestPayload = filter ? { filter } : {};

const parsed = parseCelToAdvancedFilter(savedExpression, fields);
controller.openWithField("mediaNameLookup");

The panel and Filter IR store the source Lookup key. CEL also uses that source key, for example mediaNameLookup.contains("腾讯"); never emit mediaName.contains("腾讯") or a cross-object path. The resolved target field type only controls operators, value normalization, validation, and value editor options.

Host-Owned CanvasTable Header Linkage

Header filter UI is a host integration, not a package feature. For CanvasTable header menus, keep the table integration in the host and call the package controller when the user chooses 按该字段筛选:

controller.openWithField(fieldKey);

The package does not implement CanvasTable header filter UI. The package does not implement CanvasTable suffixRender. The package also does not implement header menu placement or scroll cleanup. The header action should only open the same toolbar advanced filter draft; it must not submit immediately or reload records before 确认.

Candidate Sources

User and department filter values should use identities, not display labels. Pass remote candidates through candidateSources:

const candidateSources = {
  users: { options: userOptions, loading: usersLoading, onSearch: searchUsers },
  departments: {
    options: departmentOptions,
    loading: departmentsLoading,
    onSearch: searchDepartments,
  },
};

Do not source production user or department options from field schema options, current table rows, local demo arrays, or display labels.

Out Of Scope

This package does not render Popover, Modal, Drawer, or scroll containers. It does not implement Service routes, authentication, deployment, saved views, CanvasTable header filter UI, or CanvasTable suffixRender. The package does not filter loaded table rows locally; Make record lists must send filter.expression to the Service/backend.

Published Documentation

  • Human integration guide: README.md
  • Public API contract: PUBLIC_API.md
  • Package metadata and read order for agents: package.ai.json
  • Integration recipes: recipes.json
  • Capability catalog: capabilities.json

Development

pnpm test
pnpm typecheck
pnpm build