@pipe0/react 0.1.7 → 0.2.1
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.
- package/CHANGELOG.md +33 -0
- package/dist/components/compound/effect-catalog/card.d.mts +22 -0
- package/dist/components/compound/effect-catalog/card.d.mts.map +1 -0
- package/dist/components/compound/effect-catalog/card.mjs +82 -0
- package/dist/components/compound/effect-catalog/card.mjs.map +1 -0
- package/dist/components/compound/effect-catalog/category-filter.d.mts +27 -0
- package/dist/components/compound/effect-catalog/category-filter.d.mts.map +1 -0
- package/dist/components/compound/effect-catalog/category-filter.mjs +61 -0
- package/dist/components/compound/effect-catalog/category-filter.mjs.map +1 -0
- package/dist/components/compound/effect-catalog/empty.d.mts +17 -0
- package/dist/components/compound/effect-catalog/empty.d.mts.map +1 -0
- package/dist/components/compound/effect-catalog/empty.mjs +29 -0
- package/dist/components/compound/effect-catalog/empty.mjs.map +1 -0
- package/dist/components/compound/effect-catalog/index.d.mts +6 -0
- package/dist/components/compound/effect-catalog/list.d.mts +19 -0
- package/dist/components/compound/effect-catalog/list.d.mts.map +1 -0
- package/dist/components/compound/effect-catalog/list.mjs +33 -0
- package/dist/components/compound/effect-catalog/list.mjs.map +1 -0
- package/dist/components/compound/effect-catalog/root.d.mts +31 -0
- package/dist/components/compound/effect-catalog/root.d.mts.map +1 -0
- package/dist/components/compound/effect-catalog/root.mjs +67 -0
- package/dist/components/compound/effect-catalog/root.mjs.map +1 -0
- package/dist/components/compound/effect-catalog/search-filter.d.mts +21 -0
- package/dist/components/compound/effect-catalog/search-filter.d.mts.map +1 -0
- package/dist/components/compound/effect-catalog/search-filter.mjs +47 -0
- package/dist/components/compound/effect-catalog/search-filter.mjs.map +1 -0
- package/dist/components/compound/pipe-catalog/card.mjs +1 -1
- package/dist/components/compound/pipe-catalog/category-filter.mjs +1 -1
- package/dist/components/compound/pipe-catalog/provider-filter.mjs +1 -1
- package/dist/components/compound/pipe-catalog/root.mjs +1 -1
- package/dist/components/compound/pipe-form/content.d.mts +6 -1
- package/dist/components/compound/pipe-form/content.d.mts.map +1 -1
- package/dist/components/compound/pipe-form/content.mjs +3 -2
- package/dist/components/compound/pipe-form/content.mjs.map +1 -1
- package/dist/components/compound/pipe-form/errors.d.mts.map +1 -1
- package/dist/components/compound/pipe-form/errors.mjs +5 -4
- package/dist/components/compound/pipe-form/errors.mjs.map +1 -1
- package/dist/components/compound/pipe-form/root.d.mts +3 -1
- package/dist/components/compound/pipe-form/root.d.mts.map +1 -1
- package/dist/components/compound/pipe-form/root.mjs +5 -3
- package/dist/components/compound/pipe-form/root.mjs.map +1 -1
- package/dist/components/compound/search-catalog/active-filters.mjs +1 -1
- package/dist/components/compound/search-catalog/category-filter.mjs +1 -1
- package/dist/components/compound/search-catalog/provider-filter.mjs +1 -1
- package/dist/components/compound/search-form/content.d.mts +6 -1
- package/dist/components/compound/search-form/content.d.mts.map +1 -1
- package/dist/components/compound/search-form/content.mjs +3 -2
- package/dist/components/compound/search-form/content.mjs.map +1 -1
- package/dist/components/compound/search-form/errors.d.mts.map +1 -1
- package/dist/components/compound/search-form/errors.mjs +5 -4
- package/dist/components/compound/search-form/errors.mjs.map +1 -1
- package/dist/components/compound/search-form/root.d.mts +3 -1
- package/dist/components/compound/search-form/root.d.mts.map +1 -1
- package/dist/components/compound/search-form/root.mjs +5 -3
- package/dist/components/compound/search-form/root.mjs.map +1 -1
- package/dist/components/compound/searches-catalog/active-filters.mjs +1 -1
- package/dist/components/compound/searches-catalog/category-filter.mjs +1 -1
- package/dist/components/compound/searches-catalog/provider-filter.mjs +1 -1
- package/dist/components/defaults/adapters/context-select-input.mjs +1 -1
- package/dist/components/defaults/adapters/context-select-input.mjs.map +1 -1
- package/dist/components/defaults/adapters/exact-range-input.mjs +1 -1
- package/dist/components/defaults/adapters/index.d.mts.map +1 -1
- package/dist/components/defaults/adapters/index.mjs +8 -5
- package/dist/components/defaults/adapters/index.mjs.map +1 -1
- package/dist/components/defaults/adapters/int-input.mjs.map +1 -1
- package/dist/components/defaults/adapters/json-extraction-input.mjs +1 -1
- package/dist/components/defaults/adapters/loose-object-input.mjs +111 -0
- package/dist/components/defaults/adapters/loose-object-input.mjs.map +1 -0
- package/dist/components/defaults/adapters/pipes-run-if-input.mjs +69 -56
- package/dist/components/defaults/adapters/pipes-run-if-input.mjs.map +1 -1
- package/dist/components/defaults/adapters/providers-input.mjs.map +1 -1
- package/dist/components/defaults/adapters/search-payload-input.mjs +18 -0
- package/dist/components/defaults/adapters/search-payload-input.mjs.map +1 -0
- package/dist/components/defaults/adapters/select-input.mjs +46 -27
- package/dist/components/defaults/adapters/select-input.mjs.map +1 -1
- package/dist/components/defaults/catalog/card-derived.d.mts +1 -1
- package/dist/components/defaults/catalog/card-derived.d.mts.map +1 -1
- package/dist/components/defaults/catalog/card-derived.mjs +12 -6
- package/dist/components/defaults/catalog/card-derived.mjs.map +1 -1
- package/dist/components/defaults/catalog/provider-avatars.mjs +3 -3
- package/dist/components/defaults/catalog/provider-avatars.mjs.map +1 -1
- package/dist/components/defaults/form/form-empty-state.mjs +23 -0
- package/dist/components/defaults/form/form-empty-state.mjs.map +1 -0
- package/dist/components/defaults/layout/field-wrapper.d.mts.map +1 -1
- package/dist/components/defaults/layout/field-wrapper.mjs +11 -5
- package/dist/components/defaults/layout/field-wrapper.mjs.map +1 -1
- package/dist/components/defaults/layout/group.mjs +1 -1
- package/dist/components/internal/LiquidEditor/LiquidEditor.mjs +2 -2
- package/dist/components/internal/LiquidEditor/LiquidEditor.mjs.map +1 -1
- package/dist/components/internal/form-level-errors.mjs +4 -3
- package/dist/components/internal/form-level-errors.mjs.map +1 -1
- package/dist/components/internal/icons.mjs +27 -1
- package/dist/components/internal/icons.mjs.map +1 -1
- package/dist/components/ui/alert.d.mts +47 -0
- package/dist/components/ui/alert.d.mts.map +1 -0
- package/dist/components/ui/alert.mjs +66 -0
- package/dist/components/ui/alert.mjs.map +1 -0
- package/dist/context/catalog-card-context.mjs +4 -2
- package/dist/context/catalog-card-context.mjs.map +1 -1
- package/dist/context/effect-catalog-card-context.d.mts +20 -0
- package/dist/context/effect-catalog-card-context.d.mts.map +1 -0
- package/dist/context/effect-catalog-card-context.mjs +13 -0
- package/dist/context/effect-catalog-card-context.mjs.map +1 -0
- package/dist/context/effect-catalog-context.d.mts +20 -0
- package/dist/context/effect-catalog-context.d.mts.map +1 -0
- package/dist/context/effect-catalog-context.mjs +13 -0
- package/dist/context/effect-catalog-context.mjs.map +1 -0
- package/dist/context/form-context.d.mts +21 -0
- package/dist/context/form-context.d.mts.map +1 -0
- package/dist/context/form-context.mjs +11 -1
- package/dist/context/form-context.mjs.map +1 -1
- package/dist/context/form-provider.d.mts +3 -1
- package/dist/context/form-provider.d.mts.map +1 -1
- package/dist/context/form-provider.mjs +8 -2
- package/dist/context/form-provider.mjs.map +1 -1
- package/dist/hooks/use-effect-catalog-table.d.mts +33 -0
- package/dist/hooks/use-effect-catalog-table.d.mts.map +1 -0
- package/dist/hooks/use-effect-catalog-table.mjs +104 -0
- package/dist/hooks/use-effect-catalog-table.mjs.map +1 -0
- package/dist/hooks/use-form-core.mjs +8 -5
- package/dist/hooks/use-form-core.mjs.map +1 -1
- package/dist/hooks/use-pipe-catalog-table.d.mts +8 -8
- package/dist/hooks/use-pipe-catalog-table.d.mts.map +1 -1
- package/dist/hooks/use-pipe-catalog-table.mjs +2 -2
- package/dist/hooks/use-pipe-catalog-table.mjs.map +1 -1
- package/dist/hooks/use-pipe-form.d.mts.map +1 -1
- package/dist/hooks/use-pipe-form.mjs +18 -19
- package/dist/hooks/use-pipe-form.mjs.map +1 -1
- package/dist/hooks/use-search-catalog-table.d.mts +6 -6
- package/dist/hooks/use-search-form.d.mts.map +1 -1
- package/dist/hooks/use-search-form.mjs +18 -18
- package/dist/hooks/use-search-form.mjs.map +1 -1
- package/dist/hooks/use-sheet-effect-form.d.mts +35 -0
- package/dist/hooks/use-sheet-effect-form.d.mts.map +1 -0
- package/dist/hooks/use-sheet-effect-form.mjs +103 -0
- package/dist/hooks/use-sheet-effect-form.mjs.map +1 -0
- package/dist/index.d.mts +15 -3
- package/dist/index.mjs +16 -4
- package/dist/styles/pipe0-form.css +1 -1
- package/dist/types/adapters.d.mts +22 -1
- package/dist/types/adapters.d.mts.map +1 -1
- package/dist/types/catalog-adapters.d.mts +23 -3
- package/dist/types/catalog-adapters.d.mts.map +1 -1
- package/dist/types/field-props.d.mts +15 -13
- package/dist/types/field-props.d.mts.map +1 -1
- package/dist/types/form-customization.d.mts +2 -25
- package/dist/utils/build-section-handlers.mjs +9 -75
- package/dist/utils/build-section-handlers.mjs.map +1 -1
- package/dist/utils/catalog-helpers.d.mts +1 -1
- package/dist/widgets/token-pricing-badge.d.mts +1 -0
- package/dist/widgets/token-pricing-badge.mjs +55 -0
- package/dist/widgets/token-pricing-badge.mjs.map +1 -0
- package/dist/widgets/widget-strip.d.mts.map +1 -1
- package/dist/widgets/widget-strip.mjs +1 -0
- package/dist/widgets/widget-strip.mjs.map +1 -1
- package/dist/widgets/widget-view.d.mts.map +1 -1
- package/dist/widgets/widget-view.mjs +6 -0
- package/dist/widgets/widget-view.mjs.map +1 -1
- package/package.json +15 -25
- package/dist/components/defaults/adapters/key-value-list-input.mjs +0 -102
- package/dist/components/defaults/adapters/key-value-list-input.mjs.map +0 -1
- package/dist/types/form-customization.d.mts.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../../ui/select.mjs";
|
|
2
2
|
import { WidgetStrip } from "../../../widgets/widget-strip.mjs";
|
|
3
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { X } from "lucide-react";
|
|
4
5
|
|
|
5
6
|
//#region src/components/defaults/adapters/select-input.tsx
|
|
6
7
|
function OptionContent({ label, widgets }) {
|
|
@@ -10,38 +11,56 @@ function OptionContent({ label, widgets }) {
|
|
|
10
11
|
});
|
|
11
12
|
}
|
|
12
13
|
function SelectInputAdapter(field) {
|
|
13
|
-
const placeholder = field.meta.placeholder ?? "
|
|
14
|
+
const placeholder = field.meta.placeholder ?? "";
|
|
14
15
|
const selectedOption = field.options.find((o) => o.value === field.selectedValue);
|
|
15
16
|
const hasOptions = field.options.length > 0;
|
|
17
|
+
const isClearable = !field.meta.required && !!field.selectedValue;
|
|
16
18
|
return /* @__PURE__ */ jsxs("div", {
|
|
17
19
|
"data-p0": "input",
|
|
18
20
|
className: "pz:flex pz:flex-col pz:gap-1",
|
|
19
|
-
children: [/* @__PURE__ */ jsxs(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
children:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
21
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
22
|
+
className: "pz:relative",
|
|
23
|
+
children: [/* @__PURE__ */ jsxs(Select, {
|
|
24
|
+
value: field.selectedValue || null,
|
|
25
|
+
onValueChange: (v) => {
|
|
26
|
+
if (v === null) return;
|
|
27
|
+
field.onSelect(v);
|
|
28
|
+
},
|
|
29
|
+
name: field.path,
|
|
30
|
+
disabled: !hasOptions,
|
|
31
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
32
|
+
id: field.id,
|
|
33
|
+
"aria-invalid": !!field.error,
|
|
34
|
+
className: "pz:w-full",
|
|
35
|
+
children: /* @__PURE__ */ jsx(SelectValue, {
|
|
36
|
+
placeholder,
|
|
37
|
+
className: isClearable ? "pz:pr-6" : void 0,
|
|
38
|
+
children: selectedOption ? /* @__PURE__ */ jsx(OptionContent, {
|
|
39
|
+
label: selectedOption.label,
|
|
40
|
+
widgets: selectedOption.widgets
|
|
41
|
+
}) : void 0
|
|
42
|
+
})
|
|
43
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: /* @__PURE__ */ jsx(SelectGroup, { children: field.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, {
|
|
44
|
+
value: opt.value,
|
|
45
|
+
children: /* @__PURE__ */ jsx(OptionContent, {
|
|
46
|
+
label: opt.label,
|
|
47
|
+
widgets: opt.widgets
|
|
48
|
+
})
|
|
49
|
+
}, opt.value)) }) })]
|
|
50
|
+
}), isClearable && /* @__PURE__ */ jsx("button", {
|
|
51
|
+
type: "button",
|
|
52
|
+
"aria-label": "Clear selection",
|
|
53
|
+
onClick: () => field.onSelect(""),
|
|
54
|
+
style: {
|
|
55
|
+
position: "absolute",
|
|
56
|
+
top: "50%",
|
|
57
|
+
right: "1.75rem",
|
|
58
|
+
transform: "translateY(-50%)",
|
|
59
|
+
zIndex: 1
|
|
60
|
+
},
|
|
61
|
+
className: "pz:flex pz:size-4 pz:cursor-pointer pz:items-center pz:justify-center pz:rounded pz:text-muted-foreground pz:transition-colors pz:hover:text-foreground",
|
|
62
|
+
children: /* @__PURE__ */ jsx(X, { className: "pz:size-3.5" })
|
|
63
|
+
})]
|
|
45
64
|
}), !hasOptions && /* @__PURE__ */ jsx("span", {
|
|
46
65
|
className: "pz:text-xs pz:text-muted-foreground",
|
|
47
66
|
children: "No options available"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-input.mjs","names":[],"sources":["../../../../src/components/defaults/adapters/select-input.tsx"],"sourcesContent":["import type { WidgetsByKind } from \"@pipe0/base\";\nimport type { FieldHandle } from \"../../../types/field-handle.js\";\nimport { WidgetStrip } from \"../../../widgets/widget-strip.js\";\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../../ui/select.js\";\n\nfunction OptionContent({ label, widgets }: { label: string; widgets?: WidgetsByKind }) {\n return (\n <span className=\"pz:flex pz:items-center pz:gap-2\">\n <WidgetStrip widgets={widgets} />\n <span>{label}</span>\n </span>\n );\n}\n\nexport function SelectInputAdapter(field: FieldHandle<\"select_input\">) {\n const placeholder = field.meta.placeholder ?? \"
|
|
1
|
+
{"version":3,"file":"select-input.mjs","names":[],"sources":["../../../../src/components/defaults/adapters/select-input.tsx"],"sourcesContent":["import type { WidgetsByKind } from \"@pipe0/base\";\nimport { X } from \"lucide-react\";\nimport type { FieldHandle } from \"../../../types/field-handle.js\";\nimport { WidgetStrip } from \"../../../widgets/widget-strip.js\";\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"../../ui/select.js\";\n\nfunction OptionContent({ label, widgets }: { label: string; widgets?: WidgetsByKind }) {\n return (\n <span className=\"pz:flex pz:items-center pz:gap-2\">\n <WidgetStrip widgets={widgets} />\n <span>{label}</span>\n </span>\n );\n}\n\nexport function SelectInputAdapter(field: FieldHandle<\"select_input\">) {\n const placeholder = field.meta.placeholder ?? \"\";\n const selectedOption = field.options.find((o) => o.value === field.selectedValue);\n const hasOptions = field.options.length > 0;\n // Optional fields need a way back to the empty state — the base `Select` has\n // no built-in clear. Show an inline reset only once a value is set.\n const isClearable = !field.meta.required && !!field.selectedValue;\n\n return (\n <div data-p0=\"input\" className=\"pz:flex pz:flex-col pz:gap-1\">\n <div className=\"pz:relative\">\n <Select\n // `null` (not `undefined`) keeps the Select controlled for its whole\n // lifetime — `undefined` makes it uncontrolled while empty and flips to\n // controlled on first selection, which warns.\n value={field.selectedValue || null}\n onValueChange={(v) => {\n if (v === null) return;\n field.onSelect(v);\n }}\n name={field.path}\n disabled={!hasOptions}\n >\n <SelectTrigger id={field.id} aria-invalid={!!field.error} className=\"pz:w-full\">\n {/* Pad the value (not the trigger) so a long label doesn't run under\n the clear button — padding the trigger would shift the chevron. */}\n <SelectValue placeholder={placeholder} className={isClearable ? \"pz:pr-6\" : undefined}>\n {selectedOption ? (\n <OptionContent label={selectedOption.label} widgets={selectedOption.widgets} />\n ) : undefined}\n </SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n {field.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n <OptionContent label={opt.label} widgets={opt.widgets} />\n </SelectItem>\n ))}\n </SelectGroup>\n </SelectContent>\n </Select>\n {isClearable && (\n // A real <button>, rendered as a SIBLING of the trigger (not a child):\n // Base UI's `Select.Trigger` is a native <button> that opens on its own\n // listener, so a nested control's click is swallowed before React can\n // stop it. As an overlay sibling the event never reaches the trigger.\n // Positioned inline just left of the chevron (which keeps its default\n // far-right spot since the trigger padding is untouched).\n <button\n type=\"button\"\n aria-label=\"Clear selection\"\n onClick={() => field.onSelect(\"\")}\n style={{\n position: \"absolute\",\n top: \"50%\",\n right: \"1.75rem\",\n transform: \"translateY(-50%)\",\n zIndex: 1,\n }}\n className=\"pz:flex pz:size-4 pz:cursor-pointer pz:items-center pz:justify-center pz:rounded pz:text-muted-foreground pz:transition-colors pz:hover:text-foreground\"\n >\n <X className=\"pz:size-3.5\" />\n </button>\n )}\n </div>\n {!hasOptions && (\n <span className=\"pz:text-xs pz:text-muted-foreground\">No options available</span>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;AAaA,SAAS,cAAc,EAAE,OAAO,WAAuD;AACrF,QACE,qBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,oBAAC,aAAD,EAAsB,SAAW,GACjC,oBAAC,QAAD,YAAO,OAAa,EACf;;;AAIX,SAAgB,mBAAmB,OAAoC;CACrE,MAAM,cAAc,MAAM,KAAK,eAAe;CAC9C,MAAM,iBAAiB,MAAM,QAAQ,MAAM,MAAM,EAAE,UAAU,MAAM,cAAc;CACjF,MAAM,aAAa,MAAM,QAAQ,SAAS;CAG1C,MAAM,cAAc,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,MAAM;AAEpD,QACE,qBAAC,OAAD;EAAK,WAAQ;EAAQ,WAAU;YAA/B,CACE,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,qBAAC,QAAD;IAIE,OAAO,MAAM,iBAAiB;IAC9B,gBAAgB,MAAM;AACpB,SAAI,MAAM,KAAM;AAChB,WAAM,SAAS,EAAE;;IAEnB,MAAM,MAAM;IACZ,UAAU,CAAC;cAVb,CAYE,oBAAC,eAAD;KAAe,IAAI,MAAM;KAAI,gBAAc,CAAC,CAAC,MAAM;KAAO,WAAU;eAGlE,oBAAC,aAAD;MAA0B;MAAa,WAAW,cAAc,YAAY;gBACzE,iBACC,oBAAC,eAAD;OAAe,OAAO,eAAe;OAAO,SAAS,eAAe;OAAW,IAC7E;MACQ;KACA,GAChB,oBAAC,eAAD,YACE,oBAAC,aAAD,YACG,MAAM,QAAQ,KAAK,QAClB,oBAAC,YAAD;KAA4B,OAAO,IAAI;eACrC,oBAAC,eAAD;MAAe,OAAO,IAAI;MAAO,SAAS,IAAI;MAAW;KAC9C,EAFI,IAAI,MAER,CACb,EACU,GACA,EACT;OACR,eAOC,oBAAC,UAAD;IACE,MAAK;IACL,cAAW;IACX,eAAe,MAAM,SAAS,GAAG;IACjC,OAAO;KACL,UAAU;KACV,KAAK;KACL,OAAO;KACP,WAAW;KACX,QAAQ;KACT;IACD,WAAU;cAEV,oBAAC,GAAD,EAAG,WAAU,eAAgB;IACtB,EAEP;MACL,CAAC,cACA,oBAAC,QAAD;GAAM,WAAU;aAAsC;GAA2B,EAE/E"}
|
|
@@ -7,7 +7,7 @@ interface CatalogCreditBadgeProps {
|
|
|
7
7
|
/** Override the auto-bound credit amount. */
|
|
8
8
|
creditAmount?: number;
|
|
9
9
|
/** Override the auto-bound cost mode. */
|
|
10
|
-
costMode?: "per_result" | "per_search" | "per_page";
|
|
10
|
+
costMode?: "per_result" | "per_search" | "per_page" | "usage";
|
|
11
11
|
freeLabel?: ReactNode;
|
|
12
12
|
creditPrefix?: ReactNode;
|
|
13
13
|
className?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"card-derived.d.mts","names":[],"sources":["../../../../src/components/defaults/catalog/card-derived.tsx"],"mappings":";;;;;
|
|
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,UAqCT,sBAAA;EACf,MAAA;EACA,MAAA;EACA,OAAA,GAAU,IAAA;EAxDqB;EA0D/B,QAAA,GAAW,SAAA;EACX,gBAAA,GAAmB,SAAA;AAAA;AAAA,UAGJ,sBAAA;EACf,SAAA;EArDA;EAuDA,MAAA;EArDA;EAuDA,aAAA,IAAiB,SAAA;EArDjB;EAuDA,gBAAA,IAAoB,SAAA;EACpB,KAAA,GAAQ,SAAA;EACR,SAAA,IAAa,SAAA,aAAsB,SAAA;EACnC,SAAA;EACA,OAAA,GAAU,qBAAA;EAhEV;EAkEA,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;EAxMf;EA0MA,OAAA;EAzMA;EA2MA,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,21 +15,25 @@ 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
|
|
19
|
-
const mode = costMode ?? card
|
|
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
|
+
const display = Math.round(amount * 1e3) / 1e3;
|
|
21
22
|
return /* @__PURE__ */ jsx(CatalogCardBadge, {
|
|
22
23
|
disabled,
|
|
23
24
|
variant,
|
|
24
25
|
className: cn("pz:whitespace-nowrap", className),
|
|
25
|
-
children:
|
|
26
|
+
children: mode === "usage" ? /* @__PURE__ */ jsxs("span", {
|
|
27
|
+
className: "pz:inline-flex pz:items-center pz:gap-1",
|
|
28
|
+
children: [/* @__PURE__ */ jsx(Coins, { className: "pz:size-3" }), /* @__PURE__ */ jsx("span", { children: "Usage" })]
|
|
29
|
+
}) : display > 0 ? /* @__PURE__ */ jsxs("span", {
|
|
26
30
|
className: "pz:inline-flex pz:items-center pz:gap-0.5",
|
|
27
31
|
children: [
|
|
28
32
|
creditPrefix ? /* @__PURE__ */ jsx("span", {
|
|
29
33
|
className: "pz:mr-1",
|
|
30
34
|
children: creditPrefix
|
|
31
35
|
}) : null,
|
|
32
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
36
|
+
/* @__PURE__ */ jsx("span", { children: display }),
|
|
33
37
|
/* @__PURE__ */ jsx(Coins, { className: "pz:size-3" }),
|
|
34
38
|
/* @__PURE__ */ jsxs("span", { children: ["/ ", denom] })
|
|
35
39
|
]
|
|
@@ -106,7 +110,7 @@ function CatalogFieldBadge({ fieldType, fields, onSelectField, isFieldAvailable,
|
|
|
106
110
|
}
|
|
107
111
|
function deriveCardFields(card, fieldType) {
|
|
108
112
|
if (!card) return [];
|
|
109
|
-
if (fieldType === "output") return card.defaultOutputFields ?? [];
|
|
113
|
+
if (fieldType === "output") return ("defaultOutputFields" in card ? card.defaultOutputFields : []) ?? [];
|
|
110
114
|
if ("defaultInputFields" in card) return card.defaultInputFields.map((f) => f.name);
|
|
111
115
|
return [];
|
|
112
116
|
}
|
|
@@ -115,7 +119,8 @@ function pickOutputIndex(ctx) {
|
|
|
115
119
|
return c.pipeIdsByOutputField ?? c.searchIdsByOutputField;
|
|
116
120
|
}
|
|
117
121
|
function CatalogDocsBadge({ baseUrl, href, label = "Go to docs", className, variant = "outline" }) {
|
|
118
|
-
const
|
|
122
|
+
const card = useAnyCatalogCardOptional()?.card;
|
|
123
|
+
const docPath = card && "docPath" in card ? card.docPath : void 0;
|
|
119
124
|
const resolvedHref = href ?? (baseUrl && docPath ? `${baseUrl}${docPath}` : docPath);
|
|
120
125
|
if (!resolvedHref) return null;
|
|
121
126
|
return /* @__PURE__ */ jsx("a", {
|
|
@@ -165,6 +170,7 @@ function deriveCardId(card) {
|
|
|
165
170
|
if ("pipeId" in card) return card.pipeId;
|
|
166
171
|
if ("searchesId" in card) return card.searchesId;
|
|
167
172
|
if ("searchId" in card) return card.searchId;
|
|
173
|
+
if ("effectId" in card) return card.effectId;
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
//#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\" | \"usage\";\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 // Round away floating-point noise; flat per-use prices are already clean.\n const display = Math.round(amount * 1000) / 1000;\n return (\n <CatalogCardBadge\n disabled={disabled}\n variant={variant}\n className={cn(\"pz:whitespace-nowrap\", className)}\n >\n {mode === \"usage\" ? (\n <span className=\"pz:inline-flex pz:items-center pz:gap-1\">\n <Coins className=\"pz:size-3\" />\n <span>Usage</span>\n </span>\n ) : display > 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>{display}</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
|
|
20
|
-
const resolvedCosts = startingCostPerProvider ?? card
|
|
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
|
|
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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-wrapper.d.mts","names":[],"sources":["../../../../src/components/defaults/layout/field-wrapper.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"field-wrapper.d.mts","names":[],"sources":["../../../../src/components/defaults/layout/field-wrapper.tsx"],"mappings":";;;UASiB,wBAAA;EACf,KAAA,EAAO,aAAA;EACP,QAAA;EACA,QAAA;AAAA;AAAA,UAGe,wBAAA,SACP,SAAA,CAAU,cAAA,QAAsB,wBAAA;EACxC,KAAA,EAAO,aAAA;AAAA"}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.mjs";
|
|
2
|
-
import { useFormConfig } from "../../../context/form-context.mjs";
|
|
2
|
+
import { useFormAlert, useFormConfig } from "../../../context/form-context.mjs";
|
|
3
3
|
import { HoverInfo } from "../../hover-info.mjs";
|
|
4
4
|
import { RichText } from "../../rich-text.mjs";
|
|
5
5
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
6
6
|
import { useRender } from "@base-ui/react/use-render";
|
|
7
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Asterisk } from "lucide-react";
|
|
8
9
|
|
|
9
10
|
//#region src/components/defaults/layout/field-wrapper.tsx
|
|
10
11
|
function DefaultFieldWrapper({ field, children, className, render, ...props }) {
|
|
11
12
|
const { classNames } = useFormConfig();
|
|
13
|
+
const Alert = useFormAlert();
|
|
12
14
|
const hasError = !!field.error;
|
|
13
15
|
const isDisabled = field.disabled;
|
|
14
16
|
const defaultContent = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -16,11 +18,14 @@ function DefaultFieldWrapper({ field, children, className, render, ...props }) {
|
|
|
16
18
|
className: "pz:flex pz:flex-col pz:gap-1",
|
|
17
19
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
18
20
|
className: "pz:flex pz:items-center pz:gap-1.5",
|
|
19
|
-
children: [field.label && /* @__PURE__ */
|
|
21
|
+
children: [field.label && /* @__PURE__ */ jsxs("label", {
|
|
20
22
|
"data-p0": "label",
|
|
21
23
|
htmlFor: field.id,
|
|
22
24
|
className: classNames?.label ?? cn("pz:text-sm pz:leading-none pz:font-medium pz:select-none", hasError && "pz:text-destructive"),
|
|
23
|
-
children: /* @__PURE__ */ jsx(RichText, { children: field.label })
|
|
25
|
+
children: [/* @__PURE__ */ jsx(RichText, { children: field.label }), field.meta.required && /* @__PURE__ */ jsx(Asterisk, {
|
|
26
|
+
"aria-label": "required",
|
|
27
|
+
className: "pz:ml-0.5 pz:inline-block pz:size-3 pz:align-top pz:text-primary"
|
|
28
|
+
})]
|
|
24
29
|
}), field.info && /* @__PURE__ */ jsx(HoverInfo, { children: /* @__PURE__ */ jsx(RichText, { children: field.info }) })]
|
|
25
30
|
}), field.description && /* @__PURE__ */ jsx("p", {
|
|
26
31
|
"data-p0": "description",
|
|
@@ -32,9 +37,10 @@ function DefaultFieldWrapper({ field, children, className, render, ...props }) {
|
|
|
32
37
|
className: "pz:opacity-60 pz:pointer-events-none",
|
|
33
38
|
children
|
|
34
39
|
}) : children,
|
|
35
|
-
isDisabled && field.disabledReason && /* @__PURE__ */ jsx(
|
|
40
|
+
isDisabled && field.disabledReason && /* @__PURE__ */ jsx(Alert, {
|
|
41
|
+
variant: "info",
|
|
36
42
|
"data-p0": "disabled-reason",
|
|
37
|
-
className: classNames?.disabledReason
|
|
43
|
+
className: classNames?.disabledReason,
|
|
38
44
|
children: /* @__PURE__ */ jsx(RichText, { children: field.disabledReason })
|
|
39
45
|
}),
|
|
40
46
|
hasError && /* @__PURE__ */ jsx("span", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-wrapper.mjs","names":[],"sources":["../../../../src/components/defaults/layout/field-wrapper.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { useFormConfig } from \"../../../context/form-context.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { AnyFieldProps } from \"../../../types/field-props.js\";\nimport { HoverInfo } from \"../../hover-info.js\";\nimport { RichText } from \"../../rich-text.js\";\n\nexport interface DefaultFieldWrapperState {\n field: AnyFieldProps;\n disabled: boolean;\n hasError: boolean;\n}\n\nexport interface DefaultFieldWrapperProps\n extends useRender.ComponentProps<\"div\", DefaultFieldWrapperState> {\n field: AnyFieldProps;\n}\n\nexport function DefaultFieldWrapper({\n field,\n children,\n className,\n render,\n ...props\n}: DefaultFieldWrapperProps) {\n const { classNames } = useFormConfig();\n const hasError = !!field.error;\n const isDisabled = field.disabled;\n\n // Boolean toggles render their label inline with the switch (settings-row\n // pattern), so the wrapper's label/description block is suppressed for them.\n const inlineLabel = field.kind === \"boolean_input\";\n\n const defaultContent = (\n <>\n {!inlineLabel && (field.label || field.description || field.info) && (\n <div className=\"pz:flex pz:flex-col pz:gap-1\">\n <div className=\"pz:flex pz:items-center pz:gap-1.5\">\n {field.label && (\n <label\n data-p0=\"label\"\n htmlFor={field.id}\n className={\n classNames?.label ??\n cn(\n \"pz:text-sm pz:leading-none pz:font-medium pz:select-none\",\n hasError && \"pz:text-destructive\",\n )\n }\n >\n <RichText>{field.label}</RichText>\n </label>\n )}\n {field.info && (\n <HoverInfo>\n <RichText>{field.info}</RichText>\n </HoverInfo>\n )}\n </div>\n {field.description && (\n <p\n data-p0=\"description\"\n className={classNames?.description ?? \"pz:text-xs pz:text-muted-foreground\"}\n >\n <RichText>{field.description}</RichText>\n </p>\n )}\n </div>\n )}\n {isDisabled ? (\n <div className=\"pz:opacity-60 pz:pointer-events-none\">{children}</div>\n ) : (\n children\n )}\n {isDisabled && field.disabledReason && (\n <
|
|
1
|
+
{"version":3,"file":"field-wrapper.mjs","names":[],"sources":["../../../../src/components/defaults/layout/field-wrapper.tsx"],"sourcesContent":["import { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { Asterisk } from \"lucide-react\";\nimport { useFormAlert, useFormConfig } from \"../../../context/form-context.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { AnyFieldProps } from \"../../../types/field-props.js\";\nimport { HoverInfo } from \"../../hover-info.js\";\nimport { RichText } from \"../../rich-text.js\";\n\nexport interface DefaultFieldWrapperState {\n field: AnyFieldProps;\n disabled: boolean;\n hasError: boolean;\n}\n\nexport interface DefaultFieldWrapperProps\n extends useRender.ComponentProps<\"div\", DefaultFieldWrapperState> {\n field: AnyFieldProps;\n}\n\nexport function DefaultFieldWrapper({\n field,\n children,\n className,\n render,\n ...props\n}: DefaultFieldWrapperProps) {\n const { classNames } = useFormConfig();\n const Alert = useFormAlert();\n const hasError = !!field.error;\n const isDisabled = field.disabled;\n\n // Boolean toggles render their label inline with the switch (settings-row\n // pattern), so the wrapper's label/description block is suppressed for them.\n const inlineLabel = field.kind === \"boolean_input\";\n\n const defaultContent = (\n <>\n {!inlineLabel && (field.label || field.description || field.info) && (\n <div className=\"pz:flex pz:flex-col pz:gap-1\">\n <div className=\"pz:flex pz:items-center pz:gap-1.5\">\n {field.label && (\n <label\n data-p0=\"label\"\n htmlFor={field.id}\n className={\n classNames?.label ??\n cn(\n \"pz:text-sm pz:leading-none pz:font-medium pz:select-none\",\n hasError && \"pz:text-destructive\",\n )\n }\n >\n <RichText>{field.label}</RichText>\n {field.meta.required && (\n <Asterisk\n aria-label=\"required\"\n className=\"pz:ml-0.5 pz:inline-block pz:size-3 pz:align-top pz:text-primary\"\n />\n )}\n </label>\n )}\n {field.info && (\n <HoverInfo>\n <RichText>{field.info}</RichText>\n </HoverInfo>\n )}\n </div>\n {field.description && (\n <p\n data-p0=\"description\"\n className={classNames?.description ?? \"pz:text-xs pz:text-muted-foreground\"}\n >\n <RichText>{field.description}</RichText>\n </p>\n )}\n </div>\n )}\n {isDisabled ? (\n <div className=\"pz:opacity-60 pz:pointer-events-none\">{children}</div>\n ) : (\n children\n )}\n {isDisabled && field.disabledReason && (\n <Alert variant=\"info\" data-p0=\"disabled-reason\" className={classNames?.disabledReason}>\n <RichText>{field.disabledReason}</RichText>\n </Alert>\n )}\n {hasError && (\n <span\n data-p0=\"error\"\n className={classNames?.error ?? \"pz:text-destructive pz:text-xs pz:font-medium\"}\n role=\"alert\"\n >\n <RichText>{field.error}</RichText>\n </span>\n )}\n </>\n );\n\n return useRender({\n defaultTagName: \"div\",\n render,\n state: { field, disabled: isDisabled, hasError },\n stateAttributesMapping: {\n field: () => null,\n },\n props: mergeProps<\"div\">(\n {\n className: cn(\n classNames?.field ?? \"pz:grid pz:gap-2\",\n isDisabled && classNames?.fieldDisabled,\n className,\n ),\n children: defaultContent,\n ...({\n \"data-p0\": \"field\",\n \"data-p0-field\": field.path,\n \"data-p0-kind\": field.kind,\n ...(isDisabled ? { \"data-p0-disabled\": \"true\", \"aria-disabled\": \"true\" } : {}),\n } as Record<string, string>),\n },\n props,\n ),\n });\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAgB,oBAAoB,EAClC,OACA,UACA,WACA,QACA,GAAG,SACwB;CAC3B,MAAM,EAAE,eAAe,eAAe;CACtC,MAAM,QAAQ,cAAc;CAC5B,MAAM,WAAW,CAAC,CAAC,MAAM;CACzB,MAAM,aAAa,MAAM;CAMzB,MAAM,iBACJ;EACG,EAJe,MAAM,SAAS,qBAIb,MAAM,SAAS,MAAM,eAAe,MAAM,SAC1D,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,qBAAC,OAAD;IAAK,WAAU;cAAf,CACG,MAAM,SACL,qBAAC,SAAD;KACE,WAAQ;KACR,SAAS,MAAM;KACf,WACE,YAAY,SACZ,GACE,4DACA,YAAY,sBACb;eARL,CAWE,oBAAC,UAAD,YAAW,MAAM,OAAiB,GACjC,MAAM,KAAK,YACV,oBAAC,UAAD;MACE,cAAW;MACX,WAAU;MACV,EAEE;QAET,MAAM,QACL,oBAAC,WAAD,YACE,oBAAC,UAAD,YAAW,MAAM,MAAgB,GACvB,EAEV;OACL,MAAM,eACL,oBAAC,KAAD;IACE,WAAQ;IACR,WAAW,YAAY,eAAe;cAEtC,oBAAC,UAAD,YAAW,MAAM,aAAuB;IACtC,EAEF;;EAEP,aACC,oBAAC,OAAD;GAAK,WAAU;GAAwC;GAAe,IAEtE;EAED,cAAc,MAAM,kBACnB,oBAAC,OAAD;GAAO,SAAQ;GAAO,WAAQ;GAAkB,WAAW,YAAY;aACrE,oBAAC,UAAD,YAAW,MAAM,gBAA0B;GACrC;EAET,YACC,oBAAC,QAAD;GACE,WAAQ;GACR,WAAW,YAAY,SAAS;GAChC,MAAK;aAEL,oBAAC,UAAD,YAAW,MAAM,OAAiB;GAC7B;EAER;AAGL,QAAO,UAAU;EACf,gBAAgB;EAChB;EACA,OAAO;GAAE;GAAO,UAAU;GAAY;GAAU;EAChD,wBAAwB,EACtB,aAAa,MACd;EACD,OAAO,WACL;GACE,WAAW,GACT,YAAY,SAAS,oBACrB,cAAc,YAAY,eAC1B,UACD;GACD,UAAU;GAER,WAAW;GACX,iBAAiB,MAAM;GACvB,gBAAgB,MAAM;GACtB,GAAI,aAAa;IAAE,oBAAoB;IAAQ,iBAAiB;IAAQ,GAAG,EAAE;GAEhF,EACD,MACD;EACF,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.mjs";
|
|
2
|
+
import { IconChevronDown } from "../../internal/icons.mjs";
|
|
2
3
|
import { useFormConfig } from "../../../context/form-context.mjs";
|
|
3
4
|
import { IconGlyph } from "../../../widgets/icon-glyph.mjs";
|
|
4
|
-
import { IconChevronDown } from "../../internal/icons.mjs";
|
|
5
5
|
import { HoverInfo } from "../../hover-info.mjs";
|
|
6
6
|
import { Badge } from "../../ui/badge.mjs";
|
|
7
7
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
@@ -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:
|
|
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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\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 ` ` 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) => \" \".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 `loose_object_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, \"&\").replace(/</g, \"<\").replace(/>/g, \">\");\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 ` ` 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) => \" \".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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"}
|