@pipe0/react 0.1.7 → 0.2.0

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 (85) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/components/compound/effect-catalog/card.d.mts +22 -0
  3. package/dist/components/compound/effect-catalog/card.d.mts.map +1 -0
  4. package/dist/components/compound/effect-catalog/card.mjs +82 -0
  5. package/dist/components/compound/effect-catalog/card.mjs.map +1 -0
  6. package/dist/components/compound/effect-catalog/category-filter.d.mts +27 -0
  7. package/dist/components/compound/effect-catalog/category-filter.d.mts.map +1 -0
  8. package/dist/components/compound/effect-catalog/category-filter.mjs +61 -0
  9. package/dist/components/compound/effect-catalog/category-filter.mjs.map +1 -0
  10. package/dist/components/compound/effect-catalog/empty.d.mts +17 -0
  11. package/dist/components/compound/effect-catalog/empty.d.mts.map +1 -0
  12. package/dist/components/compound/effect-catalog/empty.mjs +29 -0
  13. package/dist/components/compound/effect-catalog/empty.mjs.map +1 -0
  14. package/dist/components/compound/effect-catalog/index.d.mts +6 -0
  15. package/dist/components/compound/effect-catalog/list.d.mts +19 -0
  16. package/dist/components/compound/effect-catalog/list.d.mts.map +1 -0
  17. package/dist/components/compound/effect-catalog/list.mjs +33 -0
  18. package/dist/components/compound/effect-catalog/list.mjs.map +1 -0
  19. package/dist/components/compound/effect-catalog/root.d.mts +31 -0
  20. package/dist/components/compound/effect-catalog/root.d.mts.map +1 -0
  21. package/dist/components/compound/effect-catalog/root.mjs +67 -0
  22. package/dist/components/compound/effect-catalog/root.mjs.map +1 -0
  23. package/dist/components/compound/effect-catalog/search-filter.d.mts +21 -0
  24. package/dist/components/compound/effect-catalog/search-filter.d.mts.map +1 -0
  25. package/dist/components/compound/effect-catalog/search-filter.mjs +47 -0
  26. package/dist/components/compound/effect-catalog/search-filter.mjs.map +1 -0
  27. package/dist/components/compound/pipe-catalog/card.mjs +1 -1
  28. package/dist/components/compound/pipe-catalog/category-filter.mjs +1 -1
  29. package/dist/components/compound/pipe-catalog/provider-filter.mjs +1 -1
  30. package/dist/components/compound/pipe-catalog/root.mjs +1 -1
  31. package/dist/components/compound/pipe-form/content.d.mts +6 -1
  32. package/dist/components/compound/pipe-form/content.d.mts.map +1 -1
  33. package/dist/components/compound/pipe-form/content.mjs +3 -2
  34. package/dist/components/compound/pipe-form/content.mjs.map +1 -1
  35. package/dist/components/compound/search-catalog/active-filters.mjs +1 -1
  36. package/dist/components/compound/search-catalog/category-filter.mjs +1 -1
  37. package/dist/components/compound/search-catalog/provider-filter.mjs +1 -1
  38. package/dist/components/compound/search-form/content.d.mts +6 -1
  39. package/dist/components/compound/search-form/content.d.mts.map +1 -1
  40. package/dist/components/compound/search-form/content.mjs +3 -2
  41. package/dist/components/compound/search-form/content.mjs.map +1 -1
  42. package/dist/components/compound/searches-catalog/active-filters.mjs +1 -1
  43. package/dist/components/compound/searches-catalog/category-filter.mjs +1 -1
  44. package/dist/components/compound/searches-catalog/provider-filter.mjs +1 -1
  45. package/dist/components/defaults/adapters/exact-range-input.mjs +1 -1
  46. package/dist/components/defaults/adapters/json-extraction-input.mjs +1 -1
  47. package/dist/components/defaults/adapters/pipes-run-if-input.mjs +1 -1
  48. package/dist/components/defaults/catalog/card-derived.d.mts.map +1 -1
  49. package/dist/components/defaults/catalog/card-derived.mjs +6 -4
  50. package/dist/components/defaults/catalog/card-derived.mjs.map +1 -1
  51. package/dist/components/defaults/catalog/provider-avatars.mjs +3 -3
  52. package/dist/components/defaults/catalog/provider-avatars.mjs.map +1 -1
  53. package/dist/components/defaults/form/form-empty-state.mjs +23 -0
  54. package/dist/components/defaults/form/form-empty-state.mjs.map +1 -0
  55. package/dist/components/internal/LiquidEditor/LiquidEditor.mjs +2 -2
  56. package/dist/components/internal/LiquidEditor/LiquidEditor.mjs.map +1 -1
  57. package/dist/context/catalog-card-context.mjs +4 -2
  58. package/dist/context/catalog-card-context.mjs.map +1 -1
  59. package/dist/context/effect-catalog-card-context.d.mts +20 -0
  60. package/dist/context/effect-catalog-card-context.d.mts.map +1 -0
  61. package/dist/context/effect-catalog-card-context.mjs +13 -0
  62. package/dist/context/effect-catalog-card-context.mjs.map +1 -0
  63. package/dist/context/effect-catalog-context.d.mts +20 -0
  64. package/dist/context/effect-catalog-context.d.mts.map +1 -0
  65. package/dist/context/effect-catalog-context.mjs +13 -0
  66. package/dist/context/effect-catalog-context.mjs.map +1 -0
  67. package/dist/hooks/use-effect-catalog-table.d.mts +33 -0
  68. package/dist/hooks/use-effect-catalog-table.d.mts.map +1 -0
  69. package/dist/hooks/use-effect-catalog-table.mjs +104 -0
  70. package/dist/hooks/use-effect-catalog-table.mjs.map +1 -0
  71. package/dist/hooks/use-form-core.mjs +1 -1
  72. package/dist/hooks/use-form-core.mjs.map +1 -1
  73. package/dist/hooks/use-sheet-effect-form.d.mts +35 -0
  74. package/dist/hooks/use-sheet-effect-form.d.mts.map +1 -0
  75. package/dist/hooks/use-sheet-effect-form.mjs +104 -0
  76. package/dist/hooks/use-sheet-effect-form.mjs.map +1 -0
  77. package/dist/index.d.mts +12 -2
  78. package/dist/index.mjs +14 -4
  79. package/dist/styles/pipe0-form.css +1 -1
  80. package/dist/types/catalog-adapters.d.mts +22 -2
  81. package/dist/types/catalog-adapters.d.mts.map +1 -1
  82. package/dist/utils/build-section-handlers.mjs +3 -3
  83. package/dist/utils/build-section-handlers.mjs.map +1 -1
  84. package/dist/utils/catalog-helpers.d.mts +1 -1
  85. package/package.json +15 -25
@@ -1,8 +1,8 @@
1
1
  import { CatalogProvider, CatalogRoot, useCatalogConfig } from "../../../context/catalog-config-context.mjs";
2
2
  import { PipeCatalogContext } from "../../../context/pipe-catalog-context.mjs";
3
+ import { DefaultCatalogRoot } from "../../defaults/catalog/layout.mjs";
3
4
  import { PipeCatalogActiveFilters } from "./active-filters.mjs";
4
5
  import { PipeCatalogCategoryFilter } from "./category-filter.mjs";
5
- import { DefaultCatalogRoot } from "../../defaults/catalog/layout.mjs";
6
6
  import { PipeCatalogColumnFilters } from "./column-filters.mjs";
7
7
  import { PipeCatalogEmpty } from "./empty.mjs";
8
8
  import { PipeCatalogList } from "./list.mjs";
@@ -1,6 +1,7 @@
1
1
  import { FormHandle, FormSectionHandle } from "../../../types/form-handle.mjs";
2
2
  import { useRender } from "@base-ui/react/use-render";
3
3
  import * as _$react from "react";
4
+ import { ReactNode } from "react";
4
5
  import { PipePayload } from "@pipe0/base";
5
6
 
6
7
  //#region src/components/compound/pipe-form/content.d.ts
@@ -11,11 +12,15 @@ interface PipeFormContentState {
11
12
  isFieldLoaderLoading: boolean;
12
13
  form: FormHandle<PipePayload>;
13
14
  }
14
- interface PipeFormContentProps extends useRender.ComponentProps<"div", PipeFormContentState> {}
15
+ interface PipeFormContentProps extends useRender.ComponentProps<"div", PipeFormContentState> {
16
+ /** Message shown when the form has no configurable fields. */
17
+ emptyMessage?: ReactNode;
18
+ }
15
19
  declare function PipeFormContent({
16
20
  children,
17
21
  className,
18
22
  render,
23
+ emptyMessage,
19
24
  ...props
20
25
  }: PipeFormContentProps): _$react.ReactElement<unknown, string | _$react.JSXElementConstructor<any>>;
21
26
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"content.d.mts","names":[],"sources":["../../../../src/components/compound/pipe-form/content.tsx"],"mappings":";;;;;;UAQiB,oBAAA;EACf,QAAA,EAAU,iBAAA;EACV,UAAA,EAAY,GAAA;EACZ,mBAAA;EACA,oBAAA;EACA,IAAA,EAAM,UAAA,CAAW,WAAA;AAAA;AAAA,UAGF,oBAAA,SACP,SAAA,CAAU,cAAA,QAAsB,oBAAA;AAAA,iBAE1B,eAAA,CAAA;EAAkB,QAAA;EAAU,SAAA;EAAW,MAAA;EAAA,GAAW;AAAA,GAAS,oBAAA,GAAoB,OAAA,CAAA,YAAA,mBAAA,OAAA,CAAA,qBAAA"}
1
+ {"version":3,"file":"content.d.mts","names":[],"sources":["../../../../src/components/compound/pipe-form/content.tsx"],"mappings":";;;;;;;UAUiB,oBAAA;EACf,QAAA,EAAU,iBAAA;EACV,UAAA,EAAY,GAAA;EACZ,mBAAA;EACA,oBAAA;EACA,IAAA,EAAM,UAAA,CAAW,WAAA;AAAA;AAAA,UAGF,oBAAA,SACP,SAAA,CAAU,cAAA,QAAsB,oBAAA;EAJvB;EAMjB,YAAA,GAAe,SAAA;AAAA;AAAA,iBAGD,eAAA,CAAA;EACd,QAAA;EACA,SAAA;EACA,MAAA;EACA,YAAA;EAAA,GACG;AAAA,GACF,oBAAA,GAAoB,OAAA,CAAA,YAAA,mBAAA,OAAA,CAAA,qBAAA"}
@@ -1,4 +1,5 @@
1
1
  import { usePipeFormContext } from "../../../context/pipe-form-context.mjs";
2
+ import { FormEmptyState } from "../../defaults/form/form-empty-state.mjs";
2
3
  import { PipeFormErrors } from "./errors.mjs";
3
4
  import { PipeFormSection } from "./section.mjs";
4
5
  import { mergeProps } from "@base-ui/react/merge-props";
@@ -6,9 +7,9 @@ import { useRender } from "@base-ui/react/use-render";
6
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
8
 
8
9
  //#region src/components/compound/pipe-form/content.tsx
9
- function PipeFormContent({ children, className, render, ...props }) {
10
+ function PipeFormContent({ children, className, render, emptyMessage, ...props }) {
10
11
  const { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form } = usePipeFormContext();
11
- const defaultBody = /* @__PURE__ */ jsxs(Fragment, { children: [sections.map((section, idx) => {
12
+ const defaultBody = /* @__PURE__ */ jsxs(Fragment, { children: [sections.length === 0 ? /* @__PURE__ */ jsx(FormEmptyState, { children: emptyMessage ?? "This pipe has no config options." }) : sections.map((section, idx) => {
12
13
  const prev = idx > 0 ? sections[idx - 1] : null;
13
14
  return /* @__PURE__ */ jsx(PipeFormSection, {
14
15
  section,
@@ -1 +1 @@
1
- {"version":3,"file":"content.mjs","names":[],"sources":["../../../../src/components/compound/pipe-form/content.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport type { PipePayload } from \"@pipe0/base\";\nimport { usePipeFormContext } from \"../../../context/pipe-form-context.js\";\nimport type { FormHandle, FormSectionHandle } from \"../../../types/form-handle.js\";\nimport { PipeFormErrors } from \"./errors.js\";\nimport { PipeFormSection } from \"./section.js\";\n\nexport interface PipeFormContentState {\n sections: FormSectionHandle[];\n fieldPaths: Set<string>;\n hasFieldLoaderError: boolean;\n isFieldLoaderLoading: boolean;\n form: FormHandle<PipePayload>;\n}\n\nexport interface PipeFormContentProps\n extends useRender.ComponentProps<\"div\", PipeFormContentState> {}\n\nexport function PipeFormContent({ children, className, render, ...props }: PipeFormContentProps) {\n const { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form } =\n usePipeFormContext();\n\n // Sections that share a label (e.g. several \"I/O & conditions\" sections)\n // render under one umbrella header. We hide the label on every section\n // except the first in each contiguous cluster.\n const defaultBody = (\n <>\n {sections.map((section, idx) => {\n const prev = idx > 0 ? sections[idx - 1] : null;\n const hideLabel = !!(section.label && prev && prev.label === section.label);\n return <PipeFormSection key={section.key} section={section} hideLabel={hideLabel} />;\n })}\n <PipeFormErrors />\n </>\n );\n\n return useRender({\n defaultTagName: \"div\",\n render,\n state: { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form },\n stateAttributesMapping: {\n sections: () => null,\n fieldPaths: () => null,\n form: () => null,\n },\n props: mergeProps<\"div\">(\n {\n className,\n children: children ?? defaultBody,\n ...({ \"data-p0\": \"form-content\" } as Record<string, string>),\n },\n props,\n ),\n });\n}\n"],"mappings":";;;;;;;;AAmBA,SAAgB,gBAAgB,EAAE,UAAU,WAAW,QAAQ,GAAG,SAA+B;CAC/F,MAAM,EAAE,UAAU,YAAY,qBAAqB,sBAAsB,SACvE,oBAAoB;CAKtB,MAAM,cACJ,4CACG,SAAS,KAAK,SAAS,QAAQ;EAC9B,MAAM,OAAO,MAAM,IAAI,SAAS,MAAM,KAAK;AAE3C,SAAO,oBAAC,iBAAD;GAA4C;GAAS,WAD1C,CAAC,EAAE,QAAQ,SAAS,QAAQ,KAAK,UAAU,QAAQ;GACe,EAAvD,QAAQ,IAA+C;GACpF,EACF,oBAAC,gBAAD,EAAkB,EACjB;AAGL,QAAO,UAAU;EACf,gBAAgB;EAChB;EACA,OAAO;GAAE;GAAU;GAAY;GAAqB;GAAsB;GAAM;EAChF,wBAAwB;GACtB,gBAAgB;GAChB,kBAAkB;GAClB,YAAY;GACb;EACD,OAAO,WACL;GACE;GACA,UAAU,YAAY;GAChB,WAAW;GAClB,EACD,MACD;EACF,CAAC"}
1
+ {"version":3,"file":"content.mjs","names":[],"sources":["../../../../src/components/compound/pipe-form/content.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport type { PipePayload } from \"@pipe0/base\";\nimport type { ReactNode } from \"react\";\nimport { usePipeFormContext } from \"../../../context/pipe-form-context.js\";\nimport type { FormHandle, FormSectionHandle } from \"../../../types/form-handle.js\";\nimport { FormEmptyState } from \"../../defaults/form/form-empty-state.js\";\nimport { PipeFormErrors } from \"./errors.js\";\nimport { PipeFormSection } from \"./section.js\";\n\nexport interface PipeFormContentState {\n sections: FormSectionHandle[];\n fieldPaths: Set<string>;\n hasFieldLoaderError: boolean;\n isFieldLoaderLoading: boolean;\n form: FormHandle<PipePayload>;\n}\n\nexport interface PipeFormContentProps\n extends useRender.ComponentProps<\"div\", PipeFormContentState> {\n /** Message shown when the form has no configurable fields. */\n emptyMessage?: ReactNode;\n}\n\nexport function PipeFormContent({\n children,\n className,\n render,\n emptyMessage,\n ...props\n}: PipeFormContentProps) {\n const { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form } =\n usePipeFormContext();\n\n // Sections that share a label (e.g. several \"I/O & conditions\" sections)\n // render under one umbrella header. We hide the label on every section\n // except the first in each contiguous cluster.\n const defaultBody = (\n <>\n {sections.length === 0 ? (\n <FormEmptyState>{emptyMessage ?? \"This pipe has no config options.\"}</FormEmptyState>\n ) : (\n sections.map((section, idx) => {\n const prev = idx > 0 ? sections[idx - 1] : null;\n const hideLabel = !!(section.label && prev && prev.label === section.label);\n return <PipeFormSection key={section.key} section={section} hideLabel={hideLabel} />;\n })\n )}\n <PipeFormErrors />\n </>\n );\n\n return useRender({\n defaultTagName: \"div\",\n render,\n state: { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form },\n stateAttributesMapping: {\n sections: () => null,\n fieldPaths: () => null,\n form: () => null,\n },\n props: mergeProps<\"div\">(\n {\n className,\n children: children ?? defaultBody,\n ...({ \"data-p0\": \"form-content\" } as Record<string, string>),\n },\n props,\n ),\n });\n}\n"],"mappings":";;;;;;;;;AAwBA,SAAgB,gBAAgB,EAC9B,UACA,WACA,QACA,cACA,GAAG,SACoB;CACvB,MAAM,EAAE,UAAU,YAAY,qBAAqB,sBAAsB,SACvE,oBAAoB;CAKtB,MAAM,cACJ,4CACG,SAAS,WAAW,IACnB,oBAAC,gBAAD,YAAiB,gBAAgB,oCAAoD,IAErF,SAAS,KAAK,SAAS,QAAQ;EAC7B,MAAM,OAAO,MAAM,IAAI,SAAS,MAAM,KAAK;AAE3C,SAAO,oBAAC,iBAAD;GAA4C;GAAS,WAD1C,CAAC,EAAE,QAAQ,SAAS,QAAQ,KAAK,UAAU,QAAQ;GACe,EAAvD,QAAQ,IAA+C;GACpF,EAEJ,oBAAC,gBAAD,EAAkB,EACjB;AAGL,QAAO,UAAU;EACf,gBAAgB;EAChB;EACA,OAAO;GAAE;GAAU;GAAY;GAAqB;GAAsB;GAAM;EAChF,wBAAwB;GACtB,gBAAgB;GAChB,kBAAkB;GAClB,YAAY;GACb;EACD,OAAO,WACL;GACE;GACA,UAAU,YAAY;GAChB,WAAW;GAClB,EACD,MACD;EACF,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { cn } from "../../../lib/utils.mjs";
2
2
  import { useCatalogConfig } from "../../../context/catalog-config-context.mjs";
3
- import { DefaultActiveFilterPill } from "../../defaults/catalog/active-filter-pill.mjs";
4
3
  import { useSearchCatalogContext } from "../../../context/search-catalog-context.mjs";
4
+ import { DefaultActiveFilterPill } from "../../defaults/catalog/active-filter-pill.mjs";
5
5
  import { mergeProps } from "@base-ui/react/merge-props";
6
6
  import { useRender } from "@base-ui/react/use-render";
7
7
  import { jsx } from "react/jsx-runtime";
@@ -3,8 +3,8 @@ import { DefaultCategoryFilter } from "../../defaults/catalog/category-filter.mj
3
3
  import { mergeProps } from "@base-ui/react/merge-props";
4
4
  import { useRender } from "@base-ui/react/use-render";
5
5
  import { jsx } from "react/jsx-runtime";
6
- import { Building2, Database, Table, User, X } from "lucide-react";
7
6
  import { getSearchCategoryEntries } from "@pipe0/base";
7
+ import { Building2, Database, Table, User, X } from "lucide-react";
8
8
 
9
9
  //#region src/components/compound/search-catalog/category-filter.tsx
10
10
  const SEARCH_CATEGORY_ICONS = {
@@ -1,8 +1,8 @@
1
1
  import { useSearchCatalogContext } from "../../../context/search-catalog-context.mjs";
2
2
  import { SearchCatalogColumnFilter } from "./column-filter.mjs";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
- import { Building2 } from "lucide-react";
5
4
  import { getProviderEntry } from "@pipe0/base";
5
+ import { Building2 } from "lucide-react";
6
6
 
7
7
  //#region src/components/compound/search-catalog/provider-filter.tsx
8
8
  const DEFAULT_PLACEHOLDER = /* @__PURE__ */ jsxs("span", {
@@ -1,6 +1,7 @@
1
1
  import { FormHandle, FormSectionHandle } from "../../../types/form-handle.mjs";
2
2
  import { useRender } from "@base-ui/react/use-render";
3
3
  import * as _$react from "react";
4
+ import { ReactNode } from "react";
4
5
  import { SearchPayload } from "@pipe0/base";
5
6
 
6
7
  //#region src/components/compound/search-form/content.d.ts
@@ -11,11 +12,15 @@ interface SearchFormContentState {
11
12
  isFieldLoaderLoading: boolean;
12
13
  form: FormHandle<SearchPayload>;
13
14
  }
14
- interface SearchFormContentProps extends useRender.ComponentProps<"div", SearchFormContentState> {}
15
+ interface SearchFormContentProps extends useRender.ComponentProps<"div", SearchFormContentState> {
16
+ /** Message shown when the form has no configurable fields. */
17
+ emptyMessage?: ReactNode;
18
+ }
15
19
  declare function SearchFormContent({
16
20
  children,
17
21
  className,
18
22
  render,
23
+ emptyMessage,
19
24
  ...props
20
25
  }: SearchFormContentProps): _$react.ReactElement<unknown, string | _$react.JSXElementConstructor<any>>;
21
26
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"content.d.mts","names":[],"sources":["../../../../src/components/compound/search-form/content.tsx"],"mappings":";;;;;;UAQiB,sBAAA;EACf,QAAA,EAAU,iBAAA;EACV,UAAA,EAAY,GAAA;EACZ,mBAAA;EACA,oBAAA;EACA,IAAA,EAAM,UAAA,CAAW,aAAA;AAAA;AAAA,UAGF,sBAAA,SACP,SAAA,CAAU,cAAA,QAAsB,sBAAA;AAAA,iBAE1B,iBAAA,CAAA;EACd,QAAA;EACA,SAAA;EACA,MAAA;EAAA,GACG;AAAA,GACF,sBAAA,GAAsB,OAAA,CAAA,YAAA,mBAAA,OAAA,CAAA,qBAAA"}
1
+ {"version":3,"file":"content.d.mts","names":[],"sources":["../../../../src/components/compound/search-form/content.tsx"],"mappings":";;;;;;;UAUiB,sBAAA;EACf,QAAA,EAAU,iBAAA;EACV,UAAA,EAAY,GAAA;EACZ,mBAAA;EACA,oBAAA;EACA,IAAA,EAAM,UAAA,CAAW,aAAA;AAAA;AAAA,UAGF,sBAAA,SACP,SAAA,CAAU,cAAA,QAAsB,sBAAA;EAJvB;EAMjB,YAAA,GAAe,SAAA;AAAA;AAAA,iBAGD,iBAAA,CAAA;EACd,QAAA;EACA,SAAA;EACA,MAAA;EACA,YAAA;EAAA,GACG;AAAA,GACF,sBAAA,GAAsB,OAAA,CAAA,YAAA,mBAAA,OAAA,CAAA,qBAAA"}
@@ -1,3 +1,4 @@
1
+ import { FormEmptyState } from "../../defaults/form/form-empty-state.mjs";
1
2
  import { useSearchFormContext } from "../../../context/search-form-context.mjs";
2
3
  import { SearchFormErrors } from "./errors.mjs";
3
4
  import { SearchFormSection } from "./section.mjs";
@@ -6,9 +7,9 @@ import { useRender } from "@base-ui/react/use-render";
6
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
8
 
8
9
  //#region src/components/compound/search-form/content.tsx
9
- function SearchFormContent({ children, className, render, ...props }) {
10
+ function SearchFormContent({ children, className, render, emptyMessage, ...props }) {
10
11
  const { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form } = useSearchFormContext();
11
- const defaultBody = /* @__PURE__ */ jsxs(Fragment, { children: [sections.map((section, idx) => {
12
+ const defaultBody = /* @__PURE__ */ jsxs(Fragment, { children: [sections.length === 0 ? /* @__PURE__ */ jsx(FormEmptyState, { children: emptyMessage ?? "This search has no config options." }) : sections.map((section, idx) => {
12
13
  const prev = idx > 0 ? sections[idx - 1] : null;
13
14
  return /* @__PURE__ */ jsx(SearchFormSection, {
14
15
  section,
@@ -1 +1 @@
1
- {"version":3,"file":"content.mjs","names":[],"sources":["../../../../src/components/compound/search-form/content.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport type { SearchPayload } from \"@pipe0/base\";\nimport { useSearchFormContext } from \"../../../context/search-form-context.js\";\nimport type { FormHandle, FormSectionHandle } from \"../../../types/form-handle.js\";\nimport { SearchFormErrors } from \"./errors.js\";\nimport { SearchFormSection } from \"./section.js\";\n\nexport interface SearchFormContentState {\n sections: FormSectionHandle[];\n fieldPaths: Set<string>;\n hasFieldLoaderError: boolean;\n isFieldLoaderLoading: boolean;\n form: FormHandle<SearchPayload>;\n}\n\nexport interface SearchFormContentProps\n extends useRender.ComponentProps<\"div\", SearchFormContentState> {}\n\nexport function SearchFormContent({\n children,\n className,\n render,\n ...props\n}: SearchFormContentProps) {\n const { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form } =\n useSearchFormContext();\n\n // Sections that share a label render under one umbrella header.\n // Hide the label on every section except the first in each contiguous cluster.\n const defaultBody = (\n <>\n {sections.map((section, idx) => {\n const prev = idx > 0 ? sections[idx - 1] : null;\n const hideLabel = !!(section.label && prev && prev.label === section.label);\n return <SearchFormSection key={section.key} section={section} hideLabel={hideLabel} />;\n })}\n <SearchFormErrors />\n </>\n );\n\n return useRender({\n defaultTagName: \"div\",\n render,\n state: { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form },\n stateAttributesMapping: {\n sections: () => null,\n fieldPaths: () => null,\n form: () => null,\n },\n props: mergeProps<\"div\">(\n {\n className,\n children: children ?? defaultBody,\n ...({ \"data-p0\": \"form-content\" } as Record<string, string>),\n },\n props,\n ),\n });\n}\n"],"mappings":";;;;;;;;AAmBA,SAAgB,kBAAkB,EAChC,UACA,WACA,QACA,GAAG,SACsB;CACzB,MAAM,EAAE,UAAU,YAAY,qBAAqB,sBAAsB,SACvE,sBAAsB;CAIxB,MAAM,cACJ,4CACG,SAAS,KAAK,SAAS,QAAQ;EAC9B,MAAM,OAAO,MAAM,IAAI,SAAS,MAAM,KAAK;AAE3C,SAAO,oBAAC,mBAAD;GAA8C;GAAS,WAD5C,CAAC,EAAE,QAAQ,SAAS,QAAQ,KAAK,UAAU,QAAQ;GACiB,EAAvD,QAAQ,IAA+C;GACtF,EACF,oBAAC,kBAAD,EAAoB,EACnB;AAGL,QAAO,UAAU;EACf,gBAAgB;EAChB;EACA,OAAO;GAAE;GAAU;GAAY;GAAqB;GAAsB;GAAM;EAChF,wBAAwB;GACtB,gBAAgB;GAChB,kBAAkB;GAClB,YAAY;GACb;EACD,OAAO,WACL;GACE;GACA,UAAU,YAAY;GAChB,WAAW;GAClB,EACD,MACD;EACF,CAAC"}
1
+ {"version":3,"file":"content.mjs","names":[],"sources":["../../../../src/components/compound/search-form/content.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport type { SearchPayload } from \"@pipe0/base\";\nimport type { ReactNode } from \"react\";\nimport { useSearchFormContext } from \"../../../context/search-form-context.js\";\nimport type { FormHandle, FormSectionHandle } from \"../../../types/form-handle.js\";\nimport { FormEmptyState } from \"../../defaults/form/form-empty-state.js\";\nimport { SearchFormErrors } from \"./errors.js\";\nimport { SearchFormSection } from \"./section.js\";\n\nexport interface SearchFormContentState {\n sections: FormSectionHandle[];\n fieldPaths: Set<string>;\n hasFieldLoaderError: boolean;\n isFieldLoaderLoading: boolean;\n form: FormHandle<SearchPayload>;\n}\n\nexport interface SearchFormContentProps\n extends useRender.ComponentProps<\"div\", SearchFormContentState> {\n /** Message shown when the form has no configurable fields. */\n emptyMessage?: ReactNode;\n}\n\nexport function SearchFormContent({\n children,\n className,\n render,\n emptyMessage,\n ...props\n}: SearchFormContentProps) {\n const { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form } =\n useSearchFormContext();\n\n // Sections that share a label render under one umbrella header.\n // Hide the label on every section except the first in each contiguous cluster.\n const defaultBody = (\n <>\n {sections.length === 0 ? (\n <FormEmptyState>{emptyMessage ?? \"This search has no config options.\"}</FormEmptyState>\n ) : (\n sections.map((section, idx) => {\n const prev = idx > 0 ? sections[idx - 1] : null;\n const hideLabel = !!(section.label && prev && prev.label === section.label);\n return <SearchFormSection key={section.key} section={section} hideLabel={hideLabel} />;\n })\n )}\n <SearchFormErrors />\n </>\n );\n\n return useRender({\n defaultTagName: \"div\",\n render,\n state: { sections, fieldPaths, hasFieldLoaderError, isFieldLoaderLoading, form },\n stateAttributesMapping: {\n sections: () => null,\n fieldPaths: () => null,\n form: () => null,\n },\n props: mergeProps<\"div\">(\n {\n className,\n children: children ?? defaultBody,\n ...({ \"data-p0\": \"form-content\" } as Record<string, string>),\n },\n props,\n ),\n });\n}\n"],"mappings":";;;;;;;;;AAwBA,SAAgB,kBAAkB,EAChC,UACA,WACA,QACA,cACA,GAAG,SACsB;CACzB,MAAM,EAAE,UAAU,YAAY,qBAAqB,sBAAsB,SACvE,sBAAsB;CAIxB,MAAM,cACJ,4CACG,SAAS,WAAW,IACnB,oBAAC,gBAAD,YAAiB,gBAAgB,sCAAsD,IAEvF,SAAS,KAAK,SAAS,QAAQ;EAC7B,MAAM,OAAO,MAAM,IAAI,SAAS,MAAM,KAAK;AAE3C,SAAO,oBAAC,mBAAD;GAA8C;GAAS,WAD5C,CAAC,EAAE,QAAQ,SAAS,QAAQ,KAAK,UAAU,QAAQ;GACiB,EAAvD,QAAQ,IAA+C;GACtF,EAEJ,oBAAC,kBAAD,EAAoB,EACnB;AAGL,QAAO,UAAU;EACf,gBAAgB;EAChB;EACA,OAAO;GAAE;GAAU;GAAY;GAAqB;GAAsB;GAAM;EAChF,wBAAwB;GACtB,gBAAgB;GAChB,kBAAkB;GAClB,YAAY;GACb;EACD,OAAO,WACL;GACE;GACA,UAAU,YAAY;GAChB,WAAW;GAClB,EACD,MACD;EACF,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { cn } from "../../../lib/utils.mjs";
2
2
  import { useCatalogConfig } from "../../../context/catalog-config-context.mjs";
3
- import { DefaultActiveFilterPill } from "../../defaults/catalog/active-filter-pill.mjs";
4
3
  import { useSearchesCatalogContext } from "../../../context/searches-catalog-context.mjs";
4
+ import { DefaultActiveFilterPill } from "../../defaults/catalog/active-filter-pill.mjs";
5
5
  import { mergeProps } from "@base-ui/react/merge-props";
6
6
  import { useRender } from "@base-ui/react/use-render";
7
7
  import { jsx } from "react/jsx-runtime";
@@ -3,8 +3,8 @@ import { DefaultCategoryFilter } from "../../defaults/catalog/category-filter.mj
3
3
  import { mergeProps } from "@base-ui/react/merge-props";
4
4
  import { useRender } from "@base-ui/react/use-render";
5
5
  import { jsx } from "react/jsx-runtime";
6
- import { Building2, Database, Table, User, X } from "lucide-react";
7
6
  import { getSearchCategoryEntries } from "@pipe0/base";
7
+ import { Building2, Database, Table, User, X } from "lucide-react";
8
8
 
9
9
  //#region src/components/compound/searches-catalog/category-filter.tsx
10
10
  const SEARCH_CATEGORY_ICONS = {
@@ -1,8 +1,8 @@
1
1
  import { useSearchesCatalogContext } from "../../../context/searches-catalog-context.mjs";
2
2
  import { SearchesCatalogColumnFilter } from "./column-filter.mjs";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
- import { Building2 } from "lucide-react";
5
4
  import { getProviderEntry } from "@pipe0/base";
5
+ import { Building2 } from "lucide-react";
6
6
 
7
7
  //#region src/components/compound/searches-catalog/provider-filter.tsx
8
8
  const DEFAULT_PLACEHOLDER = /* @__PURE__ */ jsxs("span", {
@@ -1,5 +1,5 @@
1
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/select.mjs";
2
1
  import { Input } from "../../ui/input.mjs";
2
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/select.mjs";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
 
5
5
  //#region src/components/defaults/adapters/exact-range-input.tsx
@@ -1,7 +1,7 @@
1
1
  import { cn } from "../../../lib/utils.mjs";
2
2
  import { Button } from "../../ui/button.mjs";
3
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/select.mjs";
4
3
  import { Input } from "../../ui/input.mjs";
4
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/select.mjs";
5
5
  import { IconChevronDown, IconEye, IconEyeOff, IconPlus, IconRefresh, IconTrash } from "../../internal/icons.mjs";
6
6
  import { HoverInfo } from "../../hover-info.mjs";
7
7
  import { useFieldError } from "../../../hooks/use-field-error.mjs";
@@ -1,7 +1,7 @@
1
1
  import { cn } from "../../../lib/utils.mjs";
2
2
  import { Button } from "../../ui/button.mjs";
3
- import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../../ui/select.mjs";
4
3
  import { Input } from "../../ui/input.mjs";
4
+ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../../ui/select.mjs";
5
5
  import { IconPlus } from "../../internal/icons.mjs";
6
6
  import { HoverInfo } from "../../hover-info.mjs";
7
7
  import { Badge } from "../../ui/badge.mjs";
@@ -1 +1 @@
1
- {"version":3,"file":"card-derived.d.mts","names":[],"sources":["../../../../src/components/defaults/catalog/card-derived.tsx"],"mappings":";;;;;UAoBiB,uBAAA;;EAEf,YAAA;EAFe;EAIf,QAAA;EACA,SAAA,GAAY,SAAA;EACZ,YAAA,GAAe,SAAA;EACf,SAAA;EACA,QAAA;EACA,OAAA,GAAU,qBAAA;AAAA;;;;;;iBAQI,kBAAA,CAAA;EACd,YAAA;EACA,QAAA;EACA,SAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA;EACA;AAAA,GACC,uBAAA,GAAuB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,UA6BT,sBAAA;EACf,MAAA;EACA,MAAA;EACA,OAAA,GAAU,IAAA;EAhDqB;EAkD/B,QAAA,GAAW,SAAA;EACX,gBAAA,GAAmB,SAAA;AAAA;AAAA,UAGJ,sBAAA;EACf,SAAA;EA7CA;EA+CA,MAAA;EA7CA;EA+CA,aAAA,IAAiB,SAAA;EA7CjB;EA+CA,gBAAA,IAAoB,SAAA;EACpB,KAAA,GAAQ,SAAA;EACR,SAAA,IAAa,SAAA,aAAsB,SAAA;EACnC,SAAA;EACA,OAAA,GAAU,qBAAA;EAxDV;EA0DA,MAAA,IAAU,KAAA,EAAO,sBAAA,KAA2B,SAAA;AAAA;;;;;;;iBAS9B,iBAAA,CAAA;EACd,SAAA;EACA,MAAA;EACA,aAAA;EACA,gBAAA;EACA,KAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA;EACA;AAAA,GACC,sBAAA,GAAsB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,UAyHR,qBAAA;EAjMf;EAmMA,OAAA;EAlMA;EAoMA,IAAA;EACA,KAAA,GAAQ,SAAA;EACR,SAAA;EACA,OAAA,GAAU,qBAAA;AAAA;AAAA,iBAGI,gBAAA,CAAA;EACd,OAAA;EACA,IAAA;EACA,KAAA;EACA,SAAA;EACA;AAAA,GACC,qBAAA,GAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,UAyBP,kBAAA;;EAEf,KAAA;EACA,SAAA;AAAA;AAAA,iBAGc,aAAA,CAAA;EAAgB,KAAA;EAAO;AAAA,GAAa,kBAAA,GAAkB,oBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"card-derived.d.mts","names":[],"sources":["../../../../src/components/defaults/catalog/card-derived.tsx"],"mappings":";;;;;UAgBiB,uBAAA;;EAEf,YAAA;EAFe;EAIf,QAAA;EACA,SAAA,GAAY,SAAA;EACZ,YAAA,GAAe,SAAA;EACf,SAAA;EACA,QAAA;EACA,OAAA,GAAU,qBAAA;AAAA;;;;;;iBAQI,kBAAA,CAAA;EACd,YAAA;EACA,QAAA;EACA,SAAA;EACA,YAAA;EACA,QAAA;EACA,SAAA;EACA;AAAA,GACC,uBAAA,GAAuB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,UA8BT,sBAAA;EACf,MAAA;EACA,MAAA;EACA,OAAA,GAAU,IAAA;EAjDqB;EAmD/B,QAAA,GAAW,SAAA;EACX,gBAAA,GAAmB,SAAA;AAAA;AAAA,UAGJ,sBAAA;EACf,SAAA;EA9CA;EAgDA,MAAA;EA9CA;EAgDA,aAAA,IAAiB,SAAA;EA9CjB;EAgDA,gBAAA,IAAoB,SAAA;EACpB,KAAA,GAAQ,SAAA;EACR,SAAA,IAAa,SAAA,aAAsB,SAAA;EACnC,SAAA;EACA,OAAA,GAAU,qBAAA;EAzDV;EA2DA,MAAA,IAAU,KAAA,EAAO,sBAAA,KAA2B,SAAA;AAAA;;;;;;;iBAS9B,iBAAA,CAAA;EACd,SAAA;EACA,MAAA;EACA,aAAA;EACA,gBAAA;EACA,KAAA;EACA,SAAA;EACA,SAAA;EACA,OAAA;EACA;AAAA,GACC,sBAAA,GAAsB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,UAwHR,qBAAA;EAjMf;EAmMA,OAAA;EAlMA;EAoMA,IAAA;EACA,KAAA,GAAQ,SAAA;EACR,SAAA;EACA,OAAA,GAAU,qBAAA;AAAA;AAAA,iBAGI,gBAAA,CAAA;EACd,OAAA;EACA,IAAA;EACA,KAAA;EACA,SAAA;EACA;AAAA,GACC,qBAAA,GAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,UAyBP,kBAAA;;EAEf,KAAA;EACA,SAAA;AAAA;AAAA,iBAGc,aAAA,CAAA;EAAgB,KAAA;EAAO;AAAA,GAAa,kBAAA,GAAkB,oBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -15,8 +15,8 @@ import { Check, Coins, Copy, ExternalLink, ListChecks, ListPlus } from "lucide-r
15
15
  */
16
16
  function CatalogCreditBadge({ creditAmount, costMode, freeLabel = "Free", creditPrefix = "Starts at", disabled = true, className, variant }) {
17
17
  const card = useAnyCatalogCardOptional()?.card;
18
- const amount = creditAmount ?? card?.startingCreditAmount ?? 0;
19
- const mode = costMode ?? card?.costMode ?? "per_result";
18
+ const amount = creditAmount ?? (card && "startingCreditAmount" in card ? card.startingCreditAmount : 0);
19
+ const mode = costMode ?? (card && "costMode" in card ? card.costMode : "per_result");
20
20
  const denom = mode === "per_result" ? "result" : mode === "per_search" ? "search" : "page";
21
21
  return /* @__PURE__ */ jsx(CatalogCardBadge, {
22
22
  disabled,
@@ -106,7 +106,7 @@ function CatalogFieldBadge({ fieldType, fields, onSelectField, isFieldAvailable,
106
106
  }
107
107
  function deriveCardFields(card, fieldType) {
108
108
  if (!card) return [];
109
- if (fieldType === "output") return card.defaultOutputFields ?? [];
109
+ if (fieldType === "output") return ("defaultOutputFields" in card ? card.defaultOutputFields : []) ?? [];
110
110
  if ("defaultInputFields" in card) return card.defaultInputFields.map((f) => f.name);
111
111
  return [];
112
112
  }
@@ -115,7 +115,8 @@ function pickOutputIndex(ctx) {
115
115
  return c.pipeIdsByOutputField ?? c.searchIdsByOutputField;
116
116
  }
117
117
  function CatalogDocsBadge({ baseUrl, href, label = "Go to docs", className, variant = "outline" }) {
118
- const docPath = (useAnyCatalogCardOptional()?.card)?.docPath;
118
+ const card = useAnyCatalogCardOptional()?.card;
119
+ const docPath = card && "docPath" in card ? card.docPath : void 0;
119
120
  const resolvedHref = href ?? (baseUrl && docPath ? `${baseUrl}${docPath}` : docPath);
120
121
  if (!resolvedHref) return null;
121
122
  return /* @__PURE__ */ jsx("a", {
@@ -165,6 +166,7 @@ function deriveCardId(card) {
165
166
  if ("pipeId" in card) return card.pipeId;
166
167
  if ("searchesId" in card) return card.searchesId;
167
168
  if ("searchId" in card) return card.searchId;
169
+ if ("effectId" in card) return card.effectId;
168
170
  }
169
171
 
170
172
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"card-derived.mjs","names":[],"sources":["../../../../src/components/defaults/catalog/card-derived.tsx"],"sourcesContent":["import { Check, Coins, Copy, ExternalLink, ListChecks, ListPlus } from \"lucide-react\";\nimport { type ReactNode, useState } from \"react\";\nimport {\n useAnyCatalogCardOptional,\n useAnyCatalogContextOptional,\n} from \"../../../context/catalog-card-context.js\";\nimport { useCatalogConfig } from \"../../../context/catalog-config-context.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type {\n PipeCardData,\n SearchCardData,\n SearchesCardData,\n} from \"../../../types/catalog-adapters.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../../ui/popover.js\";\nimport { CatalogCardBadge, type CatalogCardBadgeProps } from \"./card-primitives.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Credit badge — \"Starts at X / result|search|page\" or \"Free\" */\n/* -------------------------------------------------------------------------- */\n\nexport interface CatalogCreditBadgeProps {\n /** Override the auto-bound credit amount. */\n creditAmount?: number;\n /** Override the auto-bound cost mode. */\n costMode?: \"per_result\" | \"per_search\" | \"per_page\";\n freeLabel?: ReactNode;\n creditPrefix?: ReactNode;\n className?: string;\n disabled?: boolean;\n variant?: CatalogCardBadgeProps[\"variant\"];\n}\n\n/**\n * Displays the starting credit cost for the surrounding card. Self-binds to\n * `card.startingCreditAmount` + `card.costMode` from card context unless\n * overridden via props.\n */\nexport function CatalogCreditBadge({\n creditAmount,\n costMode,\n freeLabel = \"Free\",\n creditPrefix = \"Starts at\",\n disabled = true,\n className,\n variant,\n}: CatalogCreditBadgeProps) {\n const card = useAnyCatalogCardOptional()?.card;\n const amount = creditAmount ?? card?.startingCreditAmount ?? 0;\n const mode = costMode ?? card?.costMode ?? \"per_result\";\n const denom = mode === \"per_result\" ? \"result\" : mode === \"per_search\" ? \"search\" : \"page\";\n return (\n <CatalogCardBadge\n disabled={disabled}\n variant={variant}\n className={cn(\"pz:whitespace-nowrap\", className)}\n >\n {amount > 0 ? (\n <span className=\"pz:inline-flex pz:items-center pz:gap-0.5\">\n {creditPrefix ? <span className=\"pz:mr-1\">{creditPrefix}</span> : null}\n <span>{amount}</span>\n <Coins className=\"pz:size-3\" />\n <span>/ {denom}</span>\n </span>\n ) : (\n freeLabel\n )}\n </CatalogCardBadge>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Field badge — dropdown listing input or output fields, click to filter */\n/* -------------------------------------------------------------------------- */\n\nexport interface CatalogFieldBadgeState {\n fields: string[];\n isOpen: boolean;\n setOpen: (open: boolean) => void;\n /** Calls onSelectField (or the auto-wired catalog filter) and closes the popover. */\n onSelect: (fieldName: string) => void;\n isFieldAvailable: (fieldName: string) => boolean;\n}\n\nexport interface CatalogFieldBadgeProps {\n fieldType: \"input\" | \"output\";\n /** Override the auto-bound fields list. */\n fields?: string[];\n /** Override the auto-wired filter handler. */\n onSelectField?: (fieldName: string) => void;\n /** Returns true if the field has any matching cards. Defaults to a reverse-index lookup on the catalog. */\n isFieldAvailable?: (fieldName: string) => boolean;\n label?: ReactNode;\n fieldIcon?: (fieldName: string) => ReactNode;\n className?: string;\n variant?: CatalogCardBadgeProps[\"variant\"];\n /** Render the entire dropdown UI yourself. Receives the merged state. */\n render?: (state: CatalogFieldBadgeState) => ReactNode;\n}\n\n/**\n * Dropdown that lists a card's input or output fields, with click-to-filter\n * wiring. By default, clicking a field filters the catalog by that field\n * (`addColumnFilter(\"inputFields\"|\"outputFields\", field)`); pass `onSelectField`\n * to override (e.g. inverted-filter UX).\n */\nexport function CatalogFieldBadge({\n fieldType,\n fields,\n onSelectField,\n isFieldAvailable,\n label,\n fieldIcon,\n className,\n variant = \"outline\",\n render,\n}: CatalogFieldBadgeProps) {\n const [open, setOpen] = useState(false);\n const card = useAnyCatalogCardOptional()?.card;\n const ctx = useAnyCatalogContextOptional();\n\n const resolvedFields = fields ?? deriveCardFields(card, fieldType);\n\n const handleSelect =\n onSelectField ??\n ((field: string) => {\n ctx?.addColumnFilter(fieldType === \"input\" ? \"inputFields\" : \"outputFields\", field);\n });\n\n const handleAvailable =\n isFieldAvailable ??\n ((field: string) => {\n if (!ctx) return true;\n const map =\n fieldType === \"input\"\n ? (ctx as { pipeIdsByInputField?: Record<string, unknown[]> }).pipeIdsByInputField\n : pickOutputIndex(ctx);\n if (!map) return true;\n return !!map[field];\n });\n\n if (render) {\n return (\n <>\n {render({\n fields: resolvedFields,\n isOpen: open,\n setOpen,\n onSelect: (field: string) => {\n handleSelect(field);\n setOpen(false);\n },\n isFieldAvailable: handleAvailable,\n })}\n </>\n );\n }\n\n if (!resolvedFields.length) return null;\n const Icon = fieldType === \"input\" ? ListPlus : ListChecks;\n const text = label ?? (fieldType === \"input\" ? \"Show inputs\" : \"Show outputs\");\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n render={\n <CatalogCardBadge\n variant={variant}\n className={cn(\n \"pz:font-normal pz:text-muted-foreground pz:hover:text-foreground\",\n className,\n )}\n >\n <Icon className=\"pz:size-3\" />\n {text}\n </CatalogCardBadge>\n }\n />\n <PopoverContent align=\"start\" side=\"bottom\" className=\"pz:w-auto pz:min-w-40 pz:p-1\">\n <ul\n data-p0=\"catalog-card-field-badge-list\"\n className=\"pz:flex pz:flex-col pz:gap-0.5 pz:list-none pz:m-0 pz:p-0\"\n >\n {resolvedFields.map((field) => {\n const available = handleAvailable(field);\n return (\n <li key={field} className=\"pz:flex\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleSelect(field);\n setOpen(false);\n }}\n className={cn(\n \"pz:w-full pz:text-sm pz:text-left pz:py-1 pz:px-2 pz:rounded pz:flex pz:items-center pz:gap-2 pz:transition-all\",\n available\n ? \"pz:text-muted-foreground pz:hover:text-foreground pz:hover:bg-muted\"\n : \"pz:text-muted-foreground/50 pz:cursor-not-allowed\",\n )}\n data-available={available}\n >\n {fieldIcon?.(field)}\n {field}\n </button>\n </li>\n );\n })}\n </ul>\n </PopoverContent>\n </Popover>\n );\n}\n\nfunction deriveCardFields(\n card: PipeCardData | SearchCardData | SearchesCardData | undefined,\n fieldType: \"input\" | \"output\",\n): string[] {\n if (!card) return [];\n if (fieldType === \"output\") return card.defaultOutputFields ?? [];\n if (\"defaultInputFields\" in card) {\n return card.defaultInputFields.map((f) => f.name);\n }\n return [];\n}\n\nfunction pickOutputIndex(ctx: unknown): Record<string, unknown[]> | undefined {\n const c = ctx as {\n pipeIdsByOutputField?: Record<string, unknown[]>;\n searchIdsByOutputField?: Record<string, unknown[]>;\n };\n return c.pipeIdsByOutputField ?? c.searchIdsByOutputField;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Docs badge — external link to provider docs */\n/* -------------------------------------------------------------------------- */\n\nexport interface CatalogDocsBadgeProps {\n /** Base URL prepended to the card's docPath. */\n baseUrl?: string;\n /** Override the full URL (skips baseUrl + card.docPath composition). */\n href?: string;\n label?: ReactNode;\n className?: string;\n variant?: CatalogCardBadgeProps[\"variant\"];\n}\n\nexport function CatalogDocsBadge({\n baseUrl,\n href,\n label = \"Go to docs\",\n className,\n variant = \"outline\",\n}: CatalogDocsBadgeProps) {\n const card = useAnyCatalogCardOptional()?.card;\n const docPath = card?.docPath;\n const resolvedHref = href ?? (baseUrl && docPath ? `${baseUrl}${docPath}` : docPath);\n if (!resolvedHref) return null;\n return (\n <a target=\"_blank\" href={resolvedHref} rel=\"noreferrer\" onClick={(e) => e.stopPropagation()}>\n <CatalogCardBadge\n variant={variant}\n className={cn(\n \"pz:font-normal pz:text-muted-foreground pz:hover:text-foreground\",\n className,\n )}\n >\n <ExternalLink className=\"pz:size-3\" />\n {label}\n </CatalogCardBadge>\n </a>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Copy-id input — small input with a copy button */\n/* -------------------------------------------------------------------------- */\n\nexport interface CatalogCopyIdProps {\n /** Override the auto-bound id. Defaults to `card.pipeId` / `card.searchId` / `card.searchesId`. */\n value?: string;\n className?: string;\n}\n\nexport function CatalogCopyId({ value, className }: CatalogCopyIdProps) {\n const { classNames } = useCatalogConfig();\n const card = useAnyCatalogCardOptional()?.card;\n const resolved = value ?? deriveCardId(card);\n const [copied, setCopied] = useState(false);\n\n if (!resolved) return null;\n\n const handleCopy = (e: React.MouseEvent) => {\n e.stopPropagation();\n e.preventDefault();\n void navigator.clipboard?.writeText(resolved).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n });\n };\n\n return (\n <div\n data-p0=\"catalog-card-copy-id\"\n className={cn(\n \"pz:flex pz:items-center pz:gap-2 pz:rounded-md pz:border pz:border-input pz:bg-background pz:px-2 pz:py-1\",\n classNames?.copyId,\n className,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n <code className=\"pz:flex-1 pz:text-xs pz:text-muted-foreground pz:truncate pz:font-mono\">\n {resolved}\n </code>\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label=\"Copy\"\n className=\"pz:inline-flex pz:items-center pz:justify-center pz:size-6 pz:rounded pz:text-muted-foreground pz:hover:text-foreground pz:hover:bg-muted pz:transition-all\"\n >\n {copied ? <Check className=\"pz:size-3\" /> : <Copy className=\"pz:size-3\" />}\n </button>\n </div>\n );\n}\n\nfunction deriveCardId(\n card: PipeCardData | SearchCardData | SearchesCardData | undefined,\n): string | undefined {\n if (!card) return undefined;\n if (\"pipeId\" in card) return card.pipeId;\n if (\"searchesId\" in card) return card.searchesId;\n if (\"searchId\" in card) return card.searchId;\n return undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"card-derived.mjs","names":[],"sources":["../../../../src/components/defaults/catalog/card-derived.tsx"],"sourcesContent":["import { Check, Coins, Copy, ExternalLink, ListChecks, ListPlus } from \"lucide-react\";\nimport { type ReactNode, useState } from \"react\";\nimport type { AnyCardData } from \"../../../context/catalog-card-context.js\";\nimport {\n useAnyCatalogCardOptional,\n useAnyCatalogContextOptional,\n} from \"../../../context/catalog-card-context.js\";\nimport { useCatalogConfig } from \"../../../context/catalog-config-context.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../../ui/popover.js\";\nimport { CatalogCardBadge, type CatalogCardBadgeProps } from \"./card-primitives.js\";\n\n/* -------------------------------------------------------------------------- */\n/* Credit badge — \"Starts at X / result|search|page\" or \"Free\" */\n/* -------------------------------------------------------------------------- */\n\nexport interface CatalogCreditBadgeProps {\n /** Override the auto-bound credit amount. */\n creditAmount?: number;\n /** Override the auto-bound cost mode. */\n costMode?: \"per_result\" | \"per_search\" | \"per_page\";\n freeLabel?: ReactNode;\n creditPrefix?: ReactNode;\n className?: string;\n disabled?: boolean;\n variant?: CatalogCardBadgeProps[\"variant\"];\n}\n\n/**\n * Displays the starting credit cost for the surrounding card. Self-binds to\n * `card.startingCreditAmount` + `card.costMode` from card context unless\n * overridden via props.\n */\nexport function CatalogCreditBadge({\n creditAmount,\n costMode,\n freeLabel = \"Free\",\n creditPrefix = \"Starts at\",\n disabled = true,\n className,\n variant,\n}: CatalogCreditBadgeProps) {\n const card = useAnyCatalogCardOptional()?.card;\n const amount =\n creditAmount ?? (card && \"startingCreditAmount\" in card ? card.startingCreditAmount : 0);\n const mode = costMode ?? (card && \"costMode\" in card ? card.costMode : \"per_result\");\n const denom = mode === \"per_result\" ? \"result\" : mode === \"per_search\" ? \"search\" : \"page\";\n return (\n <CatalogCardBadge\n disabled={disabled}\n variant={variant}\n className={cn(\"pz:whitespace-nowrap\", className)}\n >\n {amount > 0 ? (\n <span className=\"pz:inline-flex pz:items-center pz:gap-0.5\">\n {creditPrefix ? <span className=\"pz:mr-1\">{creditPrefix}</span> : null}\n <span>{amount}</span>\n <Coins className=\"pz:size-3\" />\n <span>/ {denom}</span>\n </span>\n ) : (\n freeLabel\n )}\n </CatalogCardBadge>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Field badge — dropdown listing input or output fields, click to filter */\n/* -------------------------------------------------------------------------- */\n\nexport interface CatalogFieldBadgeState {\n fields: string[];\n isOpen: boolean;\n setOpen: (open: boolean) => void;\n /** Calls onSelectField (or the auto-wired catalog filter) and closes the popover. */\n onSelect: (fieldName: string) => void;\n isFieldAvailable: (fieldName: string) => boolean;\n}\n\nexport interface CatalogFieldBadgeProps {\n fieldType: \"input\" | \"output\";\n /** Override the auto-bound fields list. */\n fields?: string[];\n /** Override the auto-wired filter handler. */\n onSelectField?: (fieldName: string) => void;\n /** Returns true if the field has any matching cards. Defaults to a reverse-index lookup on the catalog. */\n isFieldAvailable?: (fieldName: string) => boolean;\n label?: ReactNode;\n fieldIcon?: (fieldName: string) => ReactNode;\n className?: string;\n variant?: CatalogCardBadgeProps[\"variant\"];\n /** Render the entire dropdown UI yourself. Receives the merged state. */\n render?: (state: CatalogFieldBadgeState) => ReactNode;\n}\n\n/**\n * Dropdown that lists a card's input or output fields, with click-to-filter\n * wiring. By default, clicking a field filters the catalog by that field\n * (`addColumnFilter(\"inputFields\"|\"outputFields\", field)`); pass `onSelectField`\n * to override (e.g. inverted-filter UX).\n */\nexport function CatalogFieldBadge({\n fieldType,\n fields,\n onSelectField,\n isFieldAvailable,\n label,\n fieldIcon,\n className,\n variant = \"outline\",\n render,\n}: CatalogFieldBadgeProps) {\n const [open, setOpen] = useState(false);\n const card = useAnyCatalogCardOptional()?.card;\n const ctx = useAnyCatalogContextOptional();\n\n const resolvedFields = fields ?? deriveCardFields(card, fieldType);\n\n const handleSelect =\n onSelectField ??\n ((field: string) => {\n ctx?.addColumnFilter(fieldType === \"input\" ? \"inputFields\" : \"outputFields\", field);\n });\n\n const handleAvailable =\n isFieldAvailable ??\n ((field: string) => {\n if (!ctx) return true;\n const map =\n fieldType === \"input\"\n ? (ctx as { pipeIdsByInputField?: Record<string, unknown[]> }).pipeIdsByInputField\n : pickOutputIndex(ctx);\n if (!map) return true;\n return !!map[field];\n });\n\n if (render) {\n return (\n <>\n {render({\n fields: resolvedFields,\n isOpen: open,\n setOpen,\n onSelect: (field: string) => {\n handleSelect(field);\n setOpen(false);\n },\n isFieldAvailable: handleAvailable,\n })}\n </>\n );\n }\n\n if (!resolvedFields.length) return null;\n const Icon = fieldType === \"input\" ? ListPlus : ListChecks;\n const text = label ?? (fieldType === \"input\" ? \"Show inputs\" : \"Show outputs\");\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger\n render={\n <CatalogCardBadge\n variant={variant}\n className={cn(\n \"pz:font-normal pz:text-muted-foreground pz:hover:text-foreground\",\n className,\n )}\n >\n <Icon className=\"pz:size-3\" />\n {text}\n </CatalogCardBadge>\n }\n />\n <PopoverContent align=\"start\" side=\"bottom\" className=\"pz:w-auto pz:min-w-40 pz:p-1\">\n <ul\n data-p0=\"catalog-card-field-badge-list\"\n className=\"pz:flex pz:flex-col pz:gap-0.5 pz:list-none pz:m-0 pz:p-0\"\n >\n {resolvedFields.map((field) => {\n const available = handleAvailable(field);\n return (\n <li key={field} className=\"pz:flex\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleSelect(field);\n setOpen(false);\n }}\n className={cn(\n \"pz:w-full pz:text-sm pz:text-left pz:py-1 pz:px-2 pz:rounded pz:flex pz:items-center pz:gap-2 pz:transition-all\",\n available\n ? \"pz:text-muted-foreground pz:hover:text-foreground pz:hover:bg-muted\"\n : \"pz:text-muted-foreground/50 pz:cursor-not-allowed\",\n )}\n data-available={available}\n >\n {fieldIcon?.(field)}\n {field}\n </button>\n </li>\n );\n })}\n </ul>\n </PopoverContent>\n </Popover>\n );\n}\n\nfunction deriveCardFields(card: AnyCardData | undefined, fieldType: \"input\" | \"output\"): string[] {\n if (!card) return [];\n if (fieldType === \"output\") {\n return (\"defaultOutputFields\" in card ? card.defaultOutputFields : []) ?? [];\n }\n if (\"defaultInputFields\" in card) {\n return card.defaultInputFields.map((f) => f.name);\n }\n return [];\n}\n\nfunction pickOutputIndex(ctx: unknown): Record<string, unknown[]> | undefined {\n const c = ctx as {\n pipeIdsByOutputField?: Record<string, unknown[]>;\n searchIdsByOutputField?: Record<string, unknown[]>;\n };\n return c.pipeIdsByOutputField ?? c.searchIdsByOutputField;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Docs badge — external link to provider docs */\n/* -------------------------------------------------------------------------- */\n\nexport interface CatalogDocsBadgeProps {\n /** Base URL prepended to the card's docPath. */\n baseUrl?: string;\n /** Override the full URL (skips baseUrl + card.docPath composition). */\n href?: string;\n label?: ReactNode;\n className?: string;\n variant?: CatalogCardBadgeProps[\"variant\"];\n}\n\nexport function CatalogDocsBadge({\n baseUrl,\n href,\n label = \"Go to docs\",\n className,\n variant = \"outline\",\n}: CatalogDocsBadgeProps) {\n const card = useAnyCatalogCardOptional()?.card;\n const docPath = card && \"docPath\" in card ? card.docPath : undefined;\n const resolvedHref = href ?? (baseUrl && docPath ? `${baseUrl}${docPath}` : docPath);\n if (!resolvedHref) return null;\n return (\n <a target=\"_blank\" href={resolvedHref} rel=\"noreferrer\" onClick={(e) => e.stopPropagation()}>\n <CatalogCardBadge\n variant={variant}\n className={cn(\n \"pz:font-normal pz:text-muted-foreground pz:hover:text-foreground\",\n className,\n )}\n >\n <ExternalLink className=\"pz:size-3\" />\n {label}\n </CatalogCardBadge>\n </a>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* Copy-id input — small input with a copy button */\n/* -------------------------------------------------------------------------- */\n\nexport interface CatalogCopyIdProps {\n /** Override the auto-bound id. Defaults to `card.pipeId` / `card.searchId` / `card.searchesId`. */\n value?: string;\n className?: string;\n}\n\nexport function CatalogCopyId({ value, className }: CatalogCopyIdProps) {\n const { classNames } = useCatalogConfig();\n const card = useAnyCatalogCardOptional()?.card;\n const resolved = value ?? deriveCardId(card);\n const [copied, setCopied] = useState(false);\n\n if (!resolved) return null;\n\n const handleCopy = (e: React.MouseEvent) => {\n e.stopPropagation();\n e.preventDefault();\n void navigator.clipboard?.writeText(resolved).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n });\n };\n\n return (\n <div\n data-p0=\"catalog-card-copy-id\"\n className={cn(\n \"pz:flex pz:items-center pz:gap-2 pz:rounded-md pz:border pz:border-input pz:bg-background pz:px-2 pz:py-1\",\n classNames?.copyId,\n className,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n <code className=\"pz:flex-1 pz:text-xs pz:text-muted-foreground pz:truncate pz:font-mono\">\n {resolved}\n </code>\n <button\n type=\"button\"\n onClick={handleCopy}\n aria-label=\"Copy\"\n className=\"pz:inline-flex pz:items-center pz:justify-center pz:size-6 pz:rounded pz:text-muted-foreground pz:hover:text-foreground pz:hover:bg-muted pz:transition-all\"\n >\n {copied ? <Check className=\"pz:size-3\" /> : <Copy className=\"pz:size-3\" />}\n </button>\n </div>\n );\n}\n\nfunction deriveCardId(card: AnyCardData | undefined): string | undefined {\n if (!card) return undefined;\n if (\"pipeId\" in card) return card.pipeId;\n if (\"searchesId\" in card) return card.searchesId;\n if (\"searchId\" in card) return card.searchId;\n if (\"effectId\" in card) return card.effectId;\n return undefined;\n}\n"],"mappings":"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"}
@@ -3,8 +3,8 @@ import { cn } from "../../../lib/utils.mjs";
3
3
  import { useAnyCatalogCardOptional } from "../../../context/catalog-card-context.mjs";
4
4
  import { AvatarGroup } from "../../../widgets/avatar-group.mjs";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
- import { Coins } from "lucide-react";
7
6
  import { getProviderEntry } from "@pipe0/base";
7
+ import { Coins } from "lucide-react";
8
8
  import { PreviewCard } from "@base-ui/react/preview-card";
9
9
 
10
10
  //#region src/components/defaults/catalog/provider-avatars.tsx
@@ -16,8 +16,8 @@ import { PreviewCard } from "@base-ui/react/preview-card";
16
16
  function CatalogProviderAvatars({ providers, startingCostPerProvider, disableHover, renderPopover, size, showCount, className, ...rest }) {
17
17
  const card = useAnyCatalogCardOptional()?.card;
18
18
  const portalContainer = usePortalContainer();
19
- const resolvedProviders = providers ?? card?.providers ?? [];
20
- const resolvedCosts = startingCostPerProvider ?? card?.startingCostPerProvider;
19
+ const resolvedProviders = providers ?? (card && "providers" in card ? card.providers : void 0) ?? [];
20
+ const resolvedCosts = startingCostPerProvider ?? (card && "startingCostPerProvider" in card ? card.startingCostPerProvider : void 0);
21
21
  if (!resolvedProviders.length) return null;
22
22
  if (disableHover) return /* @__PURE__ */ jsx(AvatarGroup, {
23
23
  providers: resolvedProviders,
@@ -1 +1 @@
1
- {"version":3,"file":"provider-avatars.mjs","names":["PreviewCardPrimitive"],"sources":["../../../../src/components/defaults/catalog/provider-avatars.tsx"],"sourcesContent":["import { PreviewCard as PreviewCardPrimitive } from \"@base-ui/react/preview-card\";\nimport { getProviderEntry, type ProviderName } from \"@pipe0/base\";\nimport { Coins } from \"lucide-react\";\nimport type { MouseEvent, ReactNode } from \"react\";\nimport { useAnyCatalogCardOptional } from \"../../../context/catalog-card-context.js\";\nimport { usePortalContainer } from \"../../../context/portal-container-context.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { AvatarGroup, type AvatarGroupProps } from \"../../../widgets/avatar-group.js\";\n\nexport interface CatalogProviderAvatarsProps\n extends Omit<AvatarGroupProps, \"providers\" | \"render\"> {\n /** Override the providers list. Defaults to `card.providers` from card context. */\n providers?: readonly ProviderName[];\n /** Override the cost lookup. Defaults to `card.startingCostPerProvider` from card context. */\n startingCostPerProvider?: Partial<Record<ProviderName, number>>;\n /** When true, the hover popover does not appear — pure visual stack. */\n disableHover?: boolean;\n /** Fully replace the popover body. */\n renderPopover?: (state: {\n providers: readonly ProviderName[];\n startingCostPerProvider?: Partial<Record<ProviderName, number>>;\n }) => ReactNode;\n}\n\n/**\n * `<AvatarGroup>` with a hover popover that lists each provider plus its\n * starting cost. Self-binds to the surrounding catalog card context — pass\n * explicit `providers` to override.\n */\nexport function CatalogProviderAvatars({\n providers,\n startingCostPerProvider,\n disableHover,\n renderPopover,\n size,\n showCount,\n className,\n ...rest\n}: CatalogProviderAvatarsProps) {\n const card = useAnyCatalogCardOptional()?.card;\n const portalContainer = usePortalContainer();\n const resolvedProviders = providers ?? (card?.providers as ProviderName[] | undefined) ?? [];\n const resolvedCosts = startingCostPerProvider ?? card?.startingCostPerProvider;\n\n if (!resolvedProviders.length) return null;\n\n if (disableHover) {\n return (\n <AvatarGroup\n providers={resolvedProviders}\n size={size}\n showCount={showCount}\n className={className}\n onClick={(e) => e.stopPropagation()}\n {...rest}\n />\n );\n }\n\n return (\n <PreviewCardPrimitive.Root>\n <PreviewCardPrimitive.Trigger\n render={(triggerProps) => (\n <AvatarGroup\n providers={resolvedProviders}\n size={size}\n showCount={showCount}\n className={cn(\"pz:cursor-pointer\", className)}\n {...rest}\n {...(triggerProps as Record<string, unknown>)}\n onClick={(e: MouseEvent<HTMLDivElement>) => {\n e.stopPropagation();\n const triggerOnClick = (\n triggerProps as {\n onClick?: (e: MouseEvent<HTMLDivElement>) => void;\n }\n ).onClick;\n triggerOnClick?.(e);\n }}\n />\n )}\n />\n <PreviewCardPrimitive.Portal container={portalContainer}>\n <PreviewCardPrimitive.Positioner\n side=\"bottom\"\n sideOffset={4}\n className=\"pz:isolate pz:z-50\"\n >\n <PreviewCardPrimitive.Popup\n data-p0=\"catalog-provider-avatars-popup\"\n className=\"pz:rounded-lg pz:bg-popover pz:text-popover-foreground pz:p-2.5 pz:shadow-md pz:ring-1 pz:ring-foreground/10 pz:outline-none pz:w-56\"\n >\n {renderPopover ? (\n renderPopover({\n providers: resolvedProviders,\n startingCostPerProvider: resolvedCosts,\n })\n ) : (\n <DefaultPopoverBody\n providers={resolvedProviders}\n startingCostPerProvider={resolvedCosts}\n />\n )}\n </PreviewCardPrimitive.Popup>\n </PreviewCardPrimitive.Positioner>\n </PreviewCardPrimitive.Portal>\n </PreviewCardPrimitive.Root>\n );\n}\n\nfunction DefaultPopoverBody({\n providers,\n startingCostPerProvider,\n}: {\n providers: readonly ProviderName[];\n startingCostPerProvider?: Partial<Record<ProviderName, number>>;\n}) {\n return (\n <div className=\"pz:flex pz:flex-col pz:gap-1.5\">\n <h4 className=\"pz:m-0 pz:text-sm pz:font-medium\">Providers</h4>\n <ul className=\"pz:flex pz:flex-col pz:gap-1.5 pz:list-none pz:m-0 pz:p-0\">\n {providers.map((provider) => {\n const entry = getProviderEntry(provider);\n const cost = startingCostPerProvider?.[provider];\n return (\n <li key={provider} className=\"pz:flex pz:items-center pz:gap-2\">\n <img\n src={entry?.logoUrl ?? \"/placeholder.svg?height=24&width=24\"}\n alt={entry?.label ?? provider}\n className=\"pz:rounded-md pz:h-6 pz:w-6 pz:object-cover pz:bg-background pz:border pz:border-input\"\n />\n <span className=\"pz:text-sm pz:flex-1 pz:truncate\">{entry?.label ?? provider}</span>\n {startingCostPerProvider ? (\n <span className=\"pz:inline-flex pz:items-center pz:gap-1 pz:text-xs pz:text-muted-foreground\">\n <Coins className=\"pz:size-3\" />\n {cost ?? \"Free\"}\n </span>\n ) : null}\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AA6BA,SAAgB,uBAAuB,EACrC,WACA,yBACA,cACA,eACA,MACA,WACA,WACA,GAAG,QAC2B;CAC9B,MAAM,OAAO,2BAA2B,EAAE;CAC1C,MAAM,kBAAkB,oBAAoB;CAC5C,MAAM,oBAAoB,aAAc,MAAM,aAA4C,EAAE;CAC5F,MAAM,gBAAgB,2BAA2B,MAAM;AAEvD,KAAI,CAAC,kBAAkB,OAAQ,QAAO;AAEtC,KAAI,aACF,QACE,oBAAC,aAAD;EACE,WAAW;EACL;EACK;EACA;EACX,UAAU,MAAM,EAAE,iBAAiB;EACnC,GAAI;EACJ;AAIN,QACE,qBAACA,YAAqB,MAAtB,aACE,oBAACA,YAAqB,SAAtB,EACE,SAAS,iBACP,oBAAC,aAAD;EACE,WAAW;EACL;EACK;EACX,WAAW,GAAG,qBAAqB,UAAU;EAC7C,GAAI;EACJ,GAAK;EACL,UAAU,MAAkC;AAC1C,KAAE,iBAAiB;GACnB,MAAM,iBACJ,aAGA;AACF,oBAAiB,EAAE;;EAErB,GAEJ,GACF,oBAACA,YAAqB,QAAtB;EAA6B,WAAW;YACtC,oBAACA,YAAqB,YAAtB;GACE,MAAK;GACL,YAAY;GACZ,WAAU;aAEV,oBAACA,YAAqB,OAAtB;IACE,WAAQ;IACR,WAAU;cAET,gBACC,cAAc;KACZ,WAAW;KACX,yBAAyB;KAC1B,CAAC,GAEF,oBAAC,oBAAD;KACE,WAAW;KACX,yBAAyB;KACzB;IAEuB;GACG;EACN,EACJ;;AAIhC,SAAS,mBAAmB,EAC1B,WACA,2BAIC;AACD,QACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,oBAAC,MAAD;GAAI,WAAU;aAAmC;GAAc,GAC/D,oBAAC,MAAD;GAAI,WAAU;aACX,UAAU,KAAK,aAAa;IAC3B,MAAM,QAAQ,iBAAiB,SAAS;IACxC,MAAM,OAAO,0BAA0B;AACvC,WACE,qBAAC,MAAD;KAAmB,WAAU;eAA7B;MACE,oBAAC,OAAD;OACE,KAAK,OAAO,WAAW;OACvB,KAAK,OAAO,SAAS;OACrB,WAAU;OACV;MACF,oBAAC,QAAD;OAAM,WAAU;iBAAoC,OAAO,SAAS;OAAgB;MACnF,0BACC,qBAAC,QAAD;OAAM,WAAU;iBAAhB,CACE,oBAAC,OAAD,EAAO,WAAU,aAAc,GAC9B,QAAQ,OACJ;WACL;MACD;OAbI,SAaJ;KAEP;GACC,EACD"}
1
+ {"version":3,"file":"provider-avatars.mjs","names":["PreviewCardPrimitive"],"sources":["../../../../src/components/defaults/catalog/provider-avatars.tsx"],"sourcesContent":["import { PreviewCard as PreviewCardPrimitive } from \"@base-ui/react/preview-card\";\nimport { getProviderEntry, type ProviderName } from \"@pipe0/base\";\nimport { Coins } from \"lucide-react\";\nimport type { MouseEvent, ReactNode } from \"react\";\nimport { useAnyCatalogCardOptional } from \"../../../context/catalog-card-context.js\";\nimport { usePortalContainer } from \"../../../context/portal-container-context.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { AvatarGroup, type AvatarGroupProps } from \"../../../widgets/avatar-group.js\";\n\nexport interface CatalogProviderAvatarsProps\n extends Omit<AvatarGroupProps, \"providers\" | \"render\"> {\n /** Override the providers list. Defaults to `card.providers` from card context. */\n providers?: readonly ProviderName[];\n /** Override the cost lookup. Defaults to `card.startingCostPerProvider` from card context. */\n startingCostPerProvider?: Partial<Record<ProviderName, number>>;\n /** When true, the hover popover does not appear — pure visual stack. */\n disableHover?: boolean;\n /** Fully replace the popover body. */\n renderPopover?: (state: {\n providers: readonly ProviderName[];\n startingCostPerProvider?: Partial<Record<ProviderName, number>>;\n }) => ReactNode;\n}\n\n/**\n * `<AvatarGroup>` with a hover popover that lists each provider plus its\n * starting cost. Self-binds to the surrounding catalog card context — pass\n * explicit `providers` to override.\n */\nexport function CatalogProviderAvatars({\n providers,\n startingCostPerProvider,\n disableHover,\n renderPopover,\n size,\n showCount,\n className,\n ...rest\n}: CatalogProviderAvatarsProps) {\n const card = useAnyCatalogCardOptional()?.card;\n const portalContainer = usePortalContainer();\n const resolvedProviders =\n providers ??\n (card && \"providers\" in card ? (card.providers as ProviderName[] | undefined) : undefined) ??\n [];\n const resolvedCosts =\n startingCostPerProvider ??\n (card && \"startingCostPerProvider\" in card ? card.startingCostPerProvider : undefined);\n\n if (!resolvedProviders.length) return null;\n\n if (disableHover) {\n return (\n <AvatarGroup\n providers={resolvedProviders}\n size={size}\n showCount={showCount}\n className={className}\n onClick={(e) => e.stopPropagation()}\n {...rest}\n />\n );\n }\n\n return (\n <PreviewCardPrimitive.Root>\n <PreviewCardPrimitive.Trigger\n render={(triggerProps) => (\n <AvatarGroup\n providers={resolvedProviders}\n size={size}\n showCount={showCount}\n className={cn(\"pz:cursor-pointer\", className)}\n {...rest}\n {...(triggerProps as Record<string, unknown>)}\n onClick={(e: MouseEvent<HTMLDivElement>) => {\n e.stopPropagation();\n const triggerOnClick = (\n triggerProps as {\n onClick?: (e: MouseEvent<HTMLDivElement>) => void;\n }\n ).onClick;\n triggerOnClick?.(e);\n }}\n />\n )}\n />\n <PreviewCardPrimitive.Portal container={portalContainer}>\n <PreviewCardPrimitive.Positioner\n side=\"bottom\"\n sideOffset={4}\n className=\"pz:isolate pz:z-50\"\n >\n <PreviewCardPrimitive.Popup\n data-p0=\"catalog-provider-avatars-popup\"\n className=\"pz:rounded-lg pz:bg-popover pz:text-popover-foreground pz:p-2.5 pz:shadow-md pz:ring-1 pz:ring-foreground/10 pz:outline-none pz:w-56\"\n >\n {renderPopover ? (\n renderPopover({\n providers: resolvedProviders,\n startingCostPerProvider: resolvedCosts,\n })\n ) : (\n <DefaultPopoverBody\n providers={resolvedProviders}\n startingCostPerProvider={resolvedCosts}\n />\n )}\n </PreviewCardPrimitive.Popup>\n </PreviewCardPrimitive.Positioner>\n </PreviewCardPrimitive.Portal>\n </PreviewCardPrimitive.Root>\n );\n}\n\nfunction DefaultPopoverBody({\n providers,\n startingCostPerProvider,\n}: {\n providers: readonly ProviderName[];\n startingCostPerProvider?: Partial<Record<ProviderName, number>>;\n}) {\n return (\n <div className=\"pz:flex pz:flex-col pz:gap-1.5\">\n <h4 className=\"pz:m-0 pz:text-sm pz:font-medium\">Providers</h4>\n <ul className=\"pz:flex pz:flex-col pz:gap-1.5 pz:list-none pz:m-0 pz:p-0\">\n {providers.map((provider) => {\n const entry = getProviderEntry(provider);\n const cost = startingCostPerProvider?.[provider];\n return (\n <li key={provider} className=\"pz:flex pz:items-center pz:gap-2\">\n <img\n src={entry?.logoUrl ?? \"/placeholder.svg?height=24&width=24\"}\n alt={entry?.label ?? provider}\n className=\"pz:rounded-md pz:h-6 pz:w-6 pz:object-cover pz:bg-background pz:border pz:border-input\"\n />\n <span className=\"pz:text-sm pz:flex-1 pz:truncate\">{entry?.label ?? provider}</span>\n {startingCostPerProvider ? (\n <span className=\"pz:inline-flex pz:items-center pz:gap-1 pz:text-xs pz:text-muted-foreground\">\n <Coins className=\"pz:size-3\" />\n {cost ?? \"Free\"}\n </span>\n ) : null}\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AA6BA,SAAgB,uBAAuB,EACrC,WACA,yBACA,cACA,eACA,MACA,WACA,WACA,GAAG,QAC2B;CAC9B,MAAM,OAAO,2BAA2B,EAAE;CAC1C,MAAM,kBAAkB,oBAAoB;CAC5C,MAAM,oBACJ,cACC,QAAQ,eAAe,OAAQ,KAAK,YAA2C,WAChF,EAAE;CACJ,MAAM,gBACJ,4BACC,QAAQ,6BAA6B,OAAO,KAAK,0BAA0B;AAE9E,KAAI,CAAC,kBAAkB,OAAQ,QAAO;AAEtC,KAAI,aACF,QACE,oBAAC,aAAD;EACE,WAAW;EACL;EACK;EACA;EACX,UAAU,MAAM,EAAE,iBAAiB;EACnC,GAAI;EACJ;AAIN,QACE,qBAACA,YAAqB,MAAtB,aACE,oBAACA,YAAqB,SAAtB,EACE,SAAS,iBACP,oBAAC,aAAD;EACE,WAAW;EACL;EACK;EACX,WAAW,GAAG,qBAAqB,UAAU;EAC7C,GAAI;EACJ,GAAK;EACL,UAAU,MAAkC;AAC1C,KAAE,iBAAiB;GACnB,MAAM,iBACJ,aAGA;AACF,oBAAiB,EAAE;;EAErB,GAEJ,GACF,oBAACA,YAAqB,QAAtB;EAA6B,WAAW;YACtC,oBAACA,YAAqB,YAAtB;GACE,MAAK;GACL,YAAY;GACZ,WAAU;aAEV,oBAACA,YAAqB,OAAtB;IACE,WAAQ;IACR,WAAU;cAET,gBACC,cAAc;KACZ,WAAW;KACX,yBAAyB;KAC1B,CAAC,GAEF,oBAAC,oBAAD;KACE,WAAW;KACX,yBAAyB;KACzB;IAEuB;GACG;EACN,EACJ;;AAIhC,SAAS,mBAAmB,EAC1B,WACA,2BAIC;AACD,QACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,oBAAC,MAAD;GAAI,WAAU;aAAmC;GAAc,GAC/D,oBAAC,MAAD;GAAI,WAAU;aACX,UAAU,KAAK,aAAa;IAC3B,MAAM,QAAQ,iBAAiB,SAAS;IACxC,MAAM,OAAO,0BAA0B;AACvC,WACE,qBAAC,MAAD;KAAmB,WAAU;eAA7B;MACE,oBAAC,OAAD;OACE,KAAK,OAAO,WAAW;OACvB,KAAK,OAAO,SAAS;OACrB,WAAU;OACV;MACF,oBAAC,QAAD;OAAM,WAAU;iBAAoC,OAAO,SAAS;OAAgB;MACnF,0BACC,qBAAC,QAAD;OAAM,WAAU;iBAAhB,CACE,oBAAC,OAAD,EAAO,WAAU,aAAc,GAC9B,QAAQ,OACJ;WACL;MACD;OAbI,SAaJ;KAEP;GACC,EACD"}
@@ -0,0 +1,23 @@
1
+ import { cn } from "../../../lib/utils.mjs";
2
+ import { jsx } from "react/jsx-runtime";
3
+
4
+ //#region src/components/defaults/form/form-empty-state.tsx
5
+ /**
6
+ * Shown in place of the form body when a pipe/search/effect has no
7
+ * configurable fields (e.g. an effect with only a programmatic selection and
8
+ * no custom connections).
9
+ */
10
+ function FormEmptyState({ children, className }) {
11
+ return /* @__PURE__ */ jsx("div", {
12
+ "data-p0": "form-empty",
13
+ className: cn("pz:flex pz:flex-col pz:items-center pz:justify-center pz:rounded-md pz:border pz:border-dashed pz:px-6 pz:py-10 pz:text-center", className),
14
+ children: /* @__PURE__ */ jsx("p", {
15
+ className: "pz:text-sm pz:text-muted-foreground pz:m-0",
16
+ children
17
+ })
18
+ });
19
+ }
20
+
21
+ //#endregion
22
+ export { FormEmptyState };
23
+ //# sourceMappingURL=form-empty-state.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-empty-state.mjs","names":[],"sources":["../../../../src/components/defaults/form/form-empty-state.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"../../../lib/utils.js\";\n\n/**\n * Shown in place of the form body when a pipe/search/effect has no\n * configurable fields (e.g. an effect with only a programmatic selection and\n * no custom connections).\n */\nexport function FormEmptyState({\n children,\n className,\n}: {\n children?: ReactNode;\n className?: string;\n}) {\n return (\n <div\n data-p0=\"form-empty\"\n className={cn(\n \"pz:flex pz:flex-col pz:items-center pz:justify-center pz:rounded-md pz:border pz:border-dashed pz:px-6 pz:py-10 pz:text-center\",\n className,\n )}\n >\n <p className=\"pz:text-sm pz:text-muted-foreground pz:m-0\">{children}</p>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,eAAe,EAC7B,UACA,aAIC;AACD,QACE,oBAAC,OAAD;EACE,WAAQ;EACR,WAAW,GACT,kIACA,UACD;YAED,oBAAC,KAAD;GAAG,WAAU;GAA8C;GAAa;EACpE"}
@@ -100,7 +100,7 @@ function LiquidEditor({ value, onChange, inputFields, searchSecrets, searchConst
100
100
  useEffect(() => {
101
101
  if (!editor) return;
102
102
  if (editor.getText({ blockSeparator }) === (value ?? "")) return;
103
- editor.commands.setContent(textToTiptapHTML(value ?? "", multiline), false);
103
+ editor.commands.setContent(textToTiptapHTML(value ?? "", multiline), { emitUpdate: false });
104
104
  }, [
105
105
  editor,
106
106
  value,
@@ -158,7 +158,7 @@ function LiquidEditor({ value, onChange, inputFields, searchSecrets, searchConst
158
158
  ]);
159
159
  const { items: liveReferenceItems, overflow: liveOverflow } = useMemo(() => buildVisibleItems(referenceQuery), [referenceQuery, buildVisibleItems]);
160
160
  const directiveItems = useCallback(() => RECORD_FIELD_TYPES.map((fieldType) => ({
161
- title: `output: ${fieldType}`,
161
+ title: `${fieldType}`,
162
162
  keywords: [fieldType, "output"],
163
163
  onSelect: ({ editor: ed, range }) => {
164
164
  ed.chain().focus().insertContentAt(range, OUTPUT_TEMPLATE(fieldType)).run();
@@ -1 +1 @@
1
- {"version":3,"file":"LiquidEditor.mjs","names":[],"sources":["../../../../src/components/internal/LiquidEditor/LiquidEditor.tsx"],"sourcesContent":["import {\n type PipesFieldDefinitionWithName,\n RECORD_FIELD_TYPES,\n type RecordFieldType,\n} from \"@pipe0/base\";\nimport type { EditorProps } from \"@tiptap/pm/view\";\nimport { EditorContent, EditorContext, useEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useAsyncRemoteSource } from \"../../../hooks/use-async-remote-source.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { ConstantSuggestion, SecretSuggestion } from \"../../../types/field-props.js\";\nimport { FieldLegend, type LegendEntry } from \"../field-legend.js\";\nimport { SuggestionMenu } from \"../suggestion-menu/suggestion-menu.js\";\nimport type { SuggestionItem } from \"../suggestion-menu/suggestion-menu-types.js\";\nimport { type ChipHit, TagChipDecoration } from \"../tag-chip-decoration.js\";\nimport { ChipEditPopover, type ChipEditTarget } from \"./ChipEditPopover.js\";\nimport {\n buildReferenceItems,\n parseQuery,\n type ReferenceContext,\n SECTION_CAP,\n UnifiedReferencePicker,\n} from \"./UnifiedReferencePicker.js\";\n\nexport type DirectiveKind = \"output\";\n\nexport interface LiquidEditorProps {\n value: string;\n onChange: (v: string) => void;\n inputFields: PipesFieldDefinitionWithName[];\n /**\n * Per-keystroke async searcher for secrets. Called from the `/` reference\n * picker as the user types. Calls are debounced and race-guarded — only\n * the response for the latest query is rendered.\n */\n searchSecrets?: (query: string) => Promise<SecretSuggestion[]>;\n /**\n * Per-keystroke async searcher for constants. Mirrors `searchSecrets`.\n */\n searchConstants?: (query: string) => Promise<ConstantSuggestion[]>;\n multiline?: boolean;\n /**\n * When true, the editor renders within `min-h`/`max-h` bounds. When\n * false, callers control the height via `className`. Default: true.\n */\n autoGrow?: boolean;\n directives?: DirectiveKind[];\n expectedFieldType?: RecordFieldType;\n placeholder?: string;\n className?: string;\n editorProps?: EditorProps;\n /**\n * Suppress the per-editor legend. Used by `key_value_list_input` which\n * renders a single legend below the entire list rather than per-cell.\n */\n hideLegend?: boolean;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n}\n\n/**\n * Reconciles single- and multi-line text into Tiptap-friendly HTML.\n * - Single-line: collapses whitespace runs containing newlines into a\n * single space and renders as one paragraph.\n * - Multi-line: paragraphs separated by blank lines (`\\n\\n+`); soft\n * breaks within a paragraph render as `<br>`. Leading spaces on each\n * line are preserved with `&nbsp;` so JSON-shaped payloads round-trip\n * through the editor.\n */\nfunction textToTiptapHTML(text: string, multiline: boolean): string {\n if (!multiline) {\n return `<p>${escapeHtml(text.replace(/\\s*\\n+\\s*/g, \" \"))}</p>`;\n }\n return escapeHtml(text)\n .split(/\\n\\n+/)\n .map((block) => {\n const lines = block.split(/\\n/);\n const htmlLines = lines\n .map((line) => line.replace(/^ +/, (spaces) => \"&nbsp;\".repeat(spaces.length)))\n .join(\"<br>\");\n return `<p>${htmlLines}</p>`;\n })\n .join(\"\");\n}\n\nconst OUTPUT_TEMPLATE = (fieldType: RecordFieldType): string => {\n if (fieldType === \"json\") {\n return `{% output FIELD_NAME, type: \"json\", schema: \"SCHEMA_NAME\", description: \"\" %}`;\n }\n return `{% output FIELD_NAME, type: \"${fieldType}\", description: \"\" %}`;\n};\n\n/**\n * Single editor surface for every form field that accepts Liquid tags.\n * Two triggers, two cognitive categories:\n *\n * - `/` opens a unified reference picker (Fields, Secrets, Constants).\n * Prefix syntax `/f/`, `/s/`, `/c/` filters to one source. Picking\n * emits the corresponding `{{ … }}` Liquid Output.\n *\n * - `@` opens a directives picker (only when `directives.length > 0`).\n * Today the only directive is `output`, which inserts a templated\n * `{% output FIELD_NAME, type: \"...\", description: \"\" %}` block.\n *\n * Chips render via the `TagChipDecoration` extension. Click or\n * Enter/Space on a chip mounts the contextual `ChipEditPopover`.\n *\n * Backwards compat: the persisted value is plain Liquid text,\n * byte-identical to what the legacy `TagEditor` / `TextPromptEditor`\n * produced. No engine, analyzer, or metadata changes.\n */\nexport function LiquidEditor({\n value,\n onChange,\n inputFields,\n searchSecrets,\n searchConstants,\n multiline = false,\n autoGrow = true,\n directives = [],\n expectedFieldType,\n placeholder,\n className,\n editorProps,\n hideLegend = false,\n}: LiquidEditorProps) {\n const blockSeparator = multiline ? \"\\n\" : \"\";\n const supportsOutput = directives.includes(\"output\");\n const hasSecretsResolver = !!searchSecrets;\n const hasConstantsResolver = !!searchConstants;\n\n const secretSource = useAsyncRemoteSource<SecretSuggestion>({ search: searchSecrets });\n const constantSource = useAsyncRemoteSource<ConstantSuggestion>({ search: searchConstants });\n\n // Warm caches once per resolver identity so the empty-query open is hot.\n useEffect(() => {\n secretSource.prefetch();\n }, [secretSource.prefetch]);\n\n useEffect(() => {\n constantSource.prefetch();\n }, [constantSource.prefetch]);\n\n const [chipTarget, setChipTarget] = useState<ChipEditTarget | null>(null);\n\n const tagChipExtension = useMemo(\n () =>\n TagChipDecoration.configure({\n onChipClick: (hit: ChipHit, el: HTMLElement) => {\n setChipTarget({\n kind: hit.kind,\n raw: hit.raw,\n from: hit.from,\n to: hit.to,\n rect: el.getBoundingClientRect(),\n });\n },\n }),\n [],\n );\n\n const editor = useEditor({\n extensions: [\n StarterKit.configure({ hardBreak: !multiline ? false : undefined }),\n tagChipExtension,\n ],\n parseOptions: { preserveWhitespace: \"full\" },\n editorProps: {\n ...editorProps,\n attributes: {\n class: cn(\n \"pz:px-2.5 pz:py-1 pz:text-sm pz:outline-none pz:break-words\",\n multiline\n ? autoGrow\n ? \"pz:min-h-24 pz:max-h-96 pz:overflow-auto pz:whitespace-pre-wrap\"\n : \"pz:overflow-auto pz:whitespace-pre-wrap\"\n : \"pz:min-h-8\",\n className,\n ),\n ...(placeholder ? { \"data-placeholder\": placeholder } : {}),\n ...editorProps?.attributes,\n },\n handleKeyDown(view, event) {\n if (editorProps?.handleKeyDown?.(view, event)) return true;\n // Single-line: Enter must not split the document. Multi-line: let\n // StarterKit handle Enter so paragraphs and blocks behave normally.\n if (!multiline && event.key === \"Enter\") return true;\n return false;\n },\n },\n content: textToTiptapHTML(value || \"\", multiline),\n onUpdate({ editor }) {\n onChange(editor.getText({ blockSeparator }));\n },\n });\n\n // Sync the editor's document when `value` changes from outside (e.g.\n // form reset, programmatic setValue). `useEditor` only reads `content`\n // at mount; without this hook the visible UI drifts from form state.\n // The early-return short-circuits the loop that the editor's own\n // `onUpdate` would otherwise create.\n useEffect(() => {\n if (!editor) return;\n const current = editor.getText({ blockSeparator });\n if (current === (value ?? \"\")) return;\n editor.commands.setContent(textToTiptapHTML(value ?? \"\", multiline), false);\n }, [editor, value, multiline, blockSeparator]);\n\n const insertField = useCallback((name: string) => `{{ ${name} }}`, []);\n\n // Tracks the current query in the open reference picker. Drives the\n // `liveItems` recomputation below — when the async sources resolve, we\n // re-render the picker against this query without waiting for Tiptap to\n // re-call items().\n const [referenceQuery, setReferenceQuery] = useState(\"\");\n\n // In sectioned (empty-query) view, cap each kind at SECTION_CAP visible\n // rows. Users see a compact summary and type to filter; the act of\n // filtering switches to flat mode where all matches are shown.\n // Caps are applied at this layer (not inside the picker) so the array\n // passed to SuggestionMenu matches what's rendered — keyboard nav\n // operates on the visible set and never lands on a hidden row.\n const buildVisibleItems = useCallback(\n (\n query: string,\n ): {\n items: SuggestionItem<ReferenceContext>[];\n overflow: Record<ReferenceContext[\"kind\"], number>;\n } => {\n const all = buildReferenceItems(\n query,\n {\n inputFields,\n secretSuggestions: secretSource.items,\n constantSuggestions: constantSource.items,\n },\n expectedFieldType,\n insertField,\n );\n const { prefix, residual } = parseQuery(query);\n const isFlat = !!residual.trim() || prefix !== null;\n if (isFlat) {\n return { items: all, overflow: { field: 0, secret: 0, constant: 0 } };\n }\n const fields = all.filter((i) => i.context?.kind === \"field\");\n const secrets = all.filter((i) => i.context?.kind === \"secret\");\n const constants = all.filter((i) => i.context?.kind === \"constant\");\n return {\n items: [\n ...fields.slice(0, SECTION_CAP),\n ...secrets.slice(0, SECTION_CAP),\n ...constants.slice(0, SECTION_CAP),\n ],\n overflow: {\n field: Math.max(0, fields.length - SECTION_CAP),\n secret: Math.max(0, secrets.length - SECTION_CAP),\n constant: Math.max(0, constants.length - SECTION_CAP),\n },\n };\n },\n [inputFields, expectedFieldType, insertField, secretSource.items, constantSource.items],\n );\n\n const referenceItems = useCallback(\n ({ query }: { query: string }): SuggestionItem<ReferenceContext>[] => {\n setReferenceQuery(query);\n // Fire-and-forget: results land via React state in `secretSource.items`\n // / `constantSource.items` and trigger a re-render. The popup reads\n // them through `liveItems` (see below).\n secretSource.ensure(query);\n constantSource.ensure(query);\n return buildVisibleItems(query).items;\n },\n [secretSource.ensure, constantSource.ensure, buildVisibleItems],\n );\n\n // Live items derived from the current query + cached source items. This is\n // recomputed whenever an async source resolves (which updates\n // `secretSource.items` / `constantSource.items` via setState). Passed to\n // SuggestionMenu via `liveItems` so the open popup re-renders without\n // needing the user to type — Tiptap's items() callback alone cannot\n // deliver async results that arrive between keystrokes.\n const { items: liveReferenceItems, overflow: liveOverflow } = useMemo(\n () => buildVisibleItems(referenceQuery),\n [referenceQuery, buildVisibleItems],\n );\n\n const directiveItems = useCallback(\n () =>\n RECORD_FIELD_TYPES.map<SuggestionItem>((fieldType) => ({\n title: `output: ${fieldType}`,\n keywords: [fieldType, \"output\"],\n onSelect: ({ editor: ed, range }) => {\n ed.chain().focus().insertContentAt(range, OUTPUT_TEMPLATE(fieldType)).run();\n },\n })),\n [],\n );\n\n const legendEntries: LegendEntry[] = [];\n legendEntries.push({ key: \"/\", label: \"to insert a reference\" });\n if (supportsOutput) legendEntries.push({ key: \"@\", label: \"to add an output field\" });\n\n return (\n <EditorContext.Provider value={{ editor }}>\n <EditorContent editor={editor} />\n\n {!hideLegend && <FieldLegend entries={legendEntries} />}\n\n <SuggestionMenu\n editor={editor}\n char=\"/\"\n pluginKey=\"liquidEditorReferencePicker\"\n items={referenceItems}\n liveItems={liveReferenceItems}\n >\n {(props) => (\n <UnifiedReferencePicker\n items={props.items as SuggestionItem<ReferenceContext>[]}\n selectedIndex={props.selectedIndex}\n onSelect={props.onSelect as (item: SuggestionItem<ReferenceContext>) => void}\n query={props.query}\n hasSecretsResolver={hasSecretsResolver}\n hasConstantsResolver={hasConstantsResolver}\n inputFieldsCount={inputFields.length}\n secretsPending={secretSource.pending}\n constantsPending={constantSource.pending}\n overflow={liveOverflow}\n />\n )}\n </SuggestionMenu>\n\n {supportsOutput && (\n <SuggestionMenu\n editor={editor}\n char=\"@\"\n pluginKey=\"liquidEditorDirectivePicker\"\n items={directiveItems}\n >\n {({ items, selectedIndex, onSelect }) => (\n <div className=\"pz:flex pz:flex-col pz:gap-0.5 pz:p-1 pz:min-w-56\" role=\"listbox\">\n <div className=\"pz:px-2 pz:pt-1 pz:pb-0.5 pz:text-[10px] pz:font-medium pz:uppercase pz:tracking-wide pz:text-muted-foreground\">\n Add output declaration\n </div>\n {items.map((item, index) => (\n <button\n key={item.title}\n type=\"button\"\n role=\"option\"\n aria-selected={selectedIndex === index}\n onClick={() => onSelect(item)}\n className={cn(\n \"pz:flex pz:w-full pz:items-center pz:rounded-sm pz:px-2 pz:py-1.5 pz:text-sm pz:text-left pz:cursor-pointer\",\n selectedIndex === index\n ? \"pz:bg-accent pz:text-accent-foreground\"\n : \"pz:hover:bg-accent pz:hover:text-accent-foreground\",\n )}\n >\n {item.title}\n </button>\n ))}\n </div>\n )}\n </SuggestionMenu>\n )}\n\n {chipTarget && editor && (\n <ChipEditPopover editor={editor} target={chipTarget} onClose={() => setChipTarget(null)} />\n )}\n </EditorContext.Provider>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LiquidEditor.mjs","names":[],"sources":["../../../../src/components/internal/LiquidEditor/LiquidEditor.tsx"],"sourcesContent":["import {\n type PipesFieldDefinitionWithName,\n RECORD_FIELD_TYPES,\n type RecordFieldType,\n} from \"@pipe0/base\";\nimport type { EditorProps } from \"@tiptap/pm/view\";\nimport { EditorContent, EditorContext, useEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useAsyncRemoteSource } from \"../../../hooks/use-async-remote-source.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { ConstantSuggestion, SecretSuggestion } from \"../../../types/field-props.js\";\nimport { FieldLegend, type LegendEntry } from \"../field-legend.js\";\nimport { SuggestionMenu } from \"../suggestion-menu/suggestion-menu.js\";\nimport type { SuggestionItem } from \"../suggestion-menu/suggestion-menu-types.js\";\nimport { type ChipHit, TagChipDecoration } from \"../tag-chip-decoration.js\";\nimport { ChipEditPopover, type ChipEditTarget } from \"./ChipEditPopover.js\";\nimport {\n buildReferenceItems,\n parseQuery,\n type ReferenceContext,\n SECTION_CAP,\n UnifiedReferencePicker,\n} from \"./UnifiedReferencePicker.js\";\n\nexport type DirectiveKind = \"output\";\n\nexport interface LiquidEditorProps {\n value: string;\n onChange: (v: string) => void;\n inputFields: PipesFieldDefinitionWithName[];\n /**\n * Per-keystroke async searcher for secrets. Called from the `/` reference\n * picker as the user types. Calls are debounced and race-guarded — only\n * the response for the latest query is rendered.\n */\n searchSecrets?: (query: string) => Promise<SecretSuggestion[]>;\n /**\n * Per-keystroke async searcher for constants. Mirrors `searchSecrets`.\n */\n searchConstants?: (query: string) => Promise<ConstantSuggestion[]>;\n multiline?: boolean;\n /**\n * When true, the editor renders within `min-h`/`max-h` bounds. When\n * false, callers control the height via `className`. Default: true.\n */\n autoGrow?: boolean;\n directives?: DirectiveKind[];\n expectedFieldType?: RecordFieldType;\n placeholder?: string;\n className?: string;\n editorProps?: EditorProps;\n /**\n * Suppress the per-editor legend. Used by `key_value_list_input` which\n * renders a single legend below the entire list rather than per-cell.\n */\n hideLegend?: boolean;\n}\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n}\n\n/**\n * Reconciles single- and multi-line text into Tiptap-friendly HTML.\n * - Single-line: collapses whitespace runs containing newlines into a\n * single space and renders as one paragraph.\n * - Multi-line: paragraphs separated by blank lines (`\\n\\n+`); soft\n * breaks within a paragraph render as `<br>`. Leading spaces on each\n * line are preserved with `&nbsp;` so JSON-shaped payloads round-trip\n * through the editor.\n */\nfunction textToTiptapHTML(text: string, multiline: boolean): string {\n if (!multiline) {\n return `<p>${escapeHtml(text.replace(/\\s*\\n+\\s*/g, \" \"))}</p>`;\n }\n return escapeHtml(text)\n .split(/\\n\\n+/)\n .map((block) => {\n const lines = block.split(/\\n/);\n const htmlLines = lines\n .map((line) => line.replace(/^ +/, (spaces) => \"&nbsp;\".repeat(spaces.length)))\n .join(\"<br>\");\n return `<p>${htmlLines}</p>`;\n })\n .join(\"\");\n}\n\nconst OUTPUT_TEMPLATE = (fieldType: RecordFieldType): string => {\n if (fieldType === \"json\") {\n return `{% output FIELD_NAME, type: \"json\", schema: \"SCHEMA_NAME\", description: \"\" %}`;\n }\n return `{% output FIELD_NAME, type: \"${fieldType}\", description: \"\" %}`;\n};\n\n/**\n * Single editor surface for every form field that accepts Liquid tags.\n * Two triggers, two cognitive categories:\n *\n * - `/` opens a unified reference picker (Fields, Secrets, Constants).\n * Prefix syntax `/f/`, `/s/`, `/c/` filters to one source. Picking\n * emits the corresponding `{{ … }}` Liquid Output.\n *\n * - `@` opens a directives picker (only when `directives.length > 0`).\n * Today the only directive is `output`, which inserts a templated\n * `{% output FIELD_NAME, type: \"...\", description: \"\" %}` block.\n *\n * Chips render via the `TagChipDecoration` extension. Click or\n * Enter/Space on a chip mounts the contextual `ChipEditPopover`.\n *\n * Backwards compat: the persisted value is plain Liquid text,\n * byte-identical to what the legacy `TagEditor` / `TextPromptEditor`\n * produced. No engine, analyzer, or metadata changes.\n */\nexport function LiquidEditor({\n value,\n onChange,\n inputFields,\n searchSecrets,\n searchConstants,\n multiline = false,\n autoGrow = true,\n directives = [],\n expectedFieldType,\n placeholder,\n className,\n editorProps,\n hideLegend = false,\n}: LiquidEditorProps) {\n const blockSeparator = multiline ? \"\\n\" : \"\";\n const supportsOutput = directives.includes(\"output\");\n const hasSecretsResolver = !!searchSecrets;\n const hasConstantsResolver = !!searchConstants;\n\n const secretSource = useAsyncRemoteSource<SecretSuggestion>({ search: searchSecrets });\n const constantSource = useAsyncRemoteSource<ConstantSuggestion>({ search: searchConstants });\n\n // Warm caches once per resolver identity so the empty-query open is hot.\n useEffect(() => {\n secretSource.prefetch();\n }, [secretSource.prefetch]);\n\n useEffect(() => {\n constantSource.prefetch();\n }, [constantSource.prefetch]);\n\n const [chipTarget, setChipTarget] = useState<ChipEditTarget | null>(null);\n\n const tagChipExtension = useMemo(\n () =>\n TagChipDecoration.configure({\n onChipClick: (hit: ChipHit, el: HTMLElement) => {\n setChipTarget({\n kind: hit.kind,\n raw: hit.raw,\n from: hit.from,\n to: hit.to,\n rect: el.getBoundingClientRect(),\n });\n },\n }),\n [],\n );\n\n const editor = useEditor({\n extensions: [\n StarterKit.configure({ hardBreak: !multiline ? false : undefined }),\n tagChipExtension,\n ],\n parseOptions: { preserveWhitespace: \"full\" },\n editorProps: {\n ...editorProps,\n attributes: {\n class: cn(\n \"pz:px-2.5 pz:py-1 pz:text-sm pz:outline-none pz:break-words\",\n multiline\n ? autoGrow\n ? \"pz:min-h-24 pz:max-h-96 pz:overflow-auto pz:whitespace-pre-wrap\"\n : \"pz:overflow-auto pz:whitespace-pre-wrap\"\n : \"pz:min-h-8\",\n className,\n ),\n ...(placeholder ? { \"data-placeholder\": placeholder } : {}),\n ...editorProps?.attributes,\n },\n handleKeyDown(view, event) {\n if (editorProps?.handleKeyDown?.(view, event)) return true;\n // Single-line: Enter must not split the document. Multi-line: let\n // StarterKit handle Enter so paragraphs and blocks behave normally.\n if (!multiline && event.key === \"Enter\") return true;\n return false;\n },\n },\n content: textToTiptapHTML(value || \"\", multiline),\n onUpdate({ editor }) {\n onChange(editor.getText({ blockSeparator }));\n },\n });\n\n // Sync the editor's document when `value` changes from outside (e.g.\n // form reset, programmatic setValue). `useEditor` only reads `content`\n // at mount; without this hook the visible UI drifts from form state.\n // The early-return short-circuits the loop that the editor's own\n // `onUpdate` would otherwise create.\n useEffect(() => {\n if (!editor) return;\n const current = editor.getText({ blockSeparator });\n if (current === (value ?? \"\")) return;\n editor.commands.setContent(textToTiptapHTML(value ?? \"\", multiline), {\n emitUpdate: false,\n });\n }, [editor, value, multiline, blockSeparator]);\n\n const insertField = useCallback((name: string) => `{{ ${name} }}`, []);\n\n // Tracks the current query in the open reference picker. Drives the\n // `liveItems` recomputation below — when the async sources resolve, we\n // re-render the picker against this query without waiting for Tiptap to\n // re-call items().\n const [referenceQuery, setReferenceQuery] = useState(\"\");\n\n // In sectioned (empty-query) view, cap each kind at SECTION_CAP visible\n // rows. Users see a compact summary and type to filter; the act of\n // filtering switches to flat mode where all matches are shown.\n // Caps are applied at this layer (not inside the picker) so the array\n // passed to SuggestionMenu matches what's rendered — keyboard nav\n // operates on the visible set and never lands on a hidden row.\n const buildVisibleItems = useCallback(\n (\n query: string,\n ): {\n items: SuggestionItem<ReferenceContext>[];\n overflow: Record<ReferenceContext[\"kind\"], number>;\n } => {\n const all = buildReferenceItems(\n query,\n {\n inputFields,\n secretSuggestions: secretSource.items,\n constantSuggestions: constantSource.items,\n },\n expectedFieldType,\n insertField,\n );\n const { prefix, residual } = parseQuery(query);\n const isFlat = !!residual.trim() || prefix !== null;\n if (isFlat) {\n return { items: all, overflow: { field: 0, secret: 0, constant: 0 } };\n }\n const fields = all.filter((i) => i.context?.kind === \"field\");\n const secrets = all.filter((i) => i.context?.kind === \"secret\");\n const constants = all.filter((i) => i.context?.kind === \"constant\");\n return {\n items: [\n ...fields.slice(0, SECTION_CAP),\n ...secrets.slice(0, SECTION_CAP),\n ...constants.slice(0, SECTION_CAP),\n ],\n overflow: {\n field: Math.max(0, fields.length - SECTION_CAP),\n secret: Math.max(0, secrets.length - SECTION_CAP),\n constant: Math.max(0, constants.length - SECTION_CAP),\n },\n };\n },\n [inputFields, expectedFieldType, insertField, secretSource.items, constantSource.items],\n );\n\n const referenceItems = useCallback(\n ({ query }: { query: string }): SuggestionItem<ReferenceContext>[] => {\n setReferenceQuery(query);\n // Fire-and-forget: results land via React state in `secretSource.items`\n // / `constantSource.items` and trigger a re-render. The popup reads\n // them through `liveItems` (see below).\n secretSource.ensure(query);\n constantSource.ensure(query);\n return buildVisibleItems(query).items;\n },\n [secretSource.ensure, constantSource.ensure, buildVisibleItems],\n );\n\n // Live items derived from the current query + cached source items. This is\n // recomputed whenever an async source resolves (which updates\n // `secretSource.items` / `constantSource.items` via setState). Passed to\n // SuggestionMenu via `liveItems` so the open popup re-renders without\n // needing the user to type — Tiptap's items() callback alone cannot\n // deliver async results that arrive between keystrokes.\n const { items: liveReferenceItems, overflow: liveOverflow } = useMemo(\n () => buildVisibleItems(referenceQuery),\n [referenceQuery, buildVisibleItems],\n );\n\n const directiveItems = useCallback(\n () =>\n RECORD_FIELD_TYPES.map<SuggestionItem>((fieldType) => ({\n title: `${fieldType}`,\n keywords: [fieldType, \"output\"],\n onSelect: ({ editor: ed, range }) => {\n ed.chain().focus().insertContentAt(range, OUTPUT_TEMPLATE(fieldType)).run();\n },\n })),\n [],\n );\n\n const legendEntries: LegendEntry[] = [];\n legendEntries.push({ key: \"/\", label: \"to insert a reference\" });\n if (supportsOutput) legendEntries.push({ key: \"@\", label: \"to add an output field\" });\n\n return (\n <EditorContext.Provider value={{ editor }}>\n <EditorContent editor={editor} />\n\n {!hideLegend && <FieldLegend entries={legendEntries} />}\n\n <SuggestionMenu\n editor={editor}\n char=\"/\"\n pluginKey=\"liquidEditorReferencePicker\"\n items={referenceItems}\n liveItems={liveReferenceItems}\n >\n {(props) => (\n <UnifiedReferencePicker\n items={props.items as SuggestionItem<ReferenceContext>[]}\n selectedIndex={props.selectedIndex}\n onSelect={props.onSelect as (item: SuggestionItem<ReferenceContext>) => void}\n query={props.query}\n hasSecretsResolver={hasSecretsResolver}\n hasConstantsResolver={hasConstantsResolver}\n inputFieldsCount={inputFields.length}\n secretsPending={secretSource.pending}\n constantsPending={constantSource.pending}\n overflow={liveOverflow}\n />\n )}\n </SuggestionMenu>\n\n {supportsOutput && (\n <SuggestionMenu\n editor={editor}\n char=\"@\"\n pluginKey=\"liquidEditorDirectivePicker\"\n items={directiveItems}\n >\n {({ items, selectedIndex, onSelect }) => (\n <div className=\"pz:flex pz:flex-col pz:gap-0.5 pz:p-1 pz:min-w-56\" role=\"listbox\">\n <div className=\"pz:px-2 pz:pt-1 pz:pb-0.5 pz:text-[10px] pz:font-medium pz:uppercase pz:tracking-wide pz:text-muted-foreground\">\n Add output declaration\n </div>\n {items.map((item, index) => (\n <button\n key={item.title}\n type=\"button\"\n role=\"option\"\n aria-selected={selectedIndex === index}\n onClick={() => onSelect(item)}\n className={cn(\n \"pz:flex pz:w-full pz:items-center pz:rounded-sm pz:px-2 pz:py-1.5 pz:text-sm pz:text-left pz:cursor-pointer\",\n selectedIndex === index\n ? \"pz:bg-accent pz:text-accent-foreground\"\n : \"pz:hover:bg-accent pz:hover:text-accent-foreground\",\n )}\n >\n {item.title}\n </button>\n ))}\n </div>\n )}\n </SuggestionMenu>\n )}\n\n {chipTarget && editor && (\n <ChipEditPopover editor={editor} target={chipTarget} onClose={() => setChipTarget(null)} />\n )}\n </EditorContext.Provider>\n );\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
- import { PipeCatalogContext } from "./pipe-catalog-context.mjs";
1
+ import { EffectCatalogCardContext } from "./effect-catalog-card-context.mjs";
2
2
  import { PipeCatalogCardContext } from "./pipe-catalog-card-context.mjs";
3
+ import { PipeCatalogContext } from "./pipe-catalog-context.mjs";
3
4
  import { SearchCatalogCardContext } from "./search-catalog-card-context.mjs";
4
5
  import { SearchCatalogContext } from "./search-catalog-context.mjs";
5
6
  import { SearchesCatalogCardContext } from "./searches-catalog-card-context.mjs";
@@ -12,7 +13,8 @@ function useAnyCatalogCardOptional() {
12
13
  const pipe = useContext(PipeCatalogCardContext);
13
14
  const search = useContext(SearchCatalogCardContext);
14
15
  const searches = useContext(SearchesCatalogCardContext);
15
- return pipe ?? search ?? searches;
16
+ const effect = useContext(EffectCatalogCardContext);
17
+ return pipe ?? search ?? searches ?? effect;
16
18
  }
17
19
  /**
18
20
  * Returns whichever catalog root context is set (pipe / search / searches),