@pipe0/react 1.0.36 → 1.0.38
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 +18 -0
- package/dist/components/compound/effect-catalog/category-filter.mjs +1 -1
- package/dist/components/compound/effect-catalog/category-filter.mjs.map +1 -1
- package/dist/components/compound/effect-catalog/search-filter.mjs +1 -1
- package/dist/components/compound/effect-catalog/search-filter.mjs.map +1 -1
- package/dist/components/compound/pipe-catalog/category-filter.mjs +1 -1
- package/dist/components/compound/pipe-catalog/category-filter.mjs.map +1 -1
- package/dist/components/compound/pipe-catalog/input-field-filter.mjs +1 -1
- package/dist/components/compound/pipe-catalog/input-field-filter.mjs.map +1 -1
- package/dist/components/compound/pipe-catalog/output-field-filter.mjs +1 -1
- package/dist/components/compound/pipe-catalog/output-field-filter.mjs.map +1 -1
- package/dist/components/compound/pipe-catalog/provider-filter.mjs +1 -1
- package/dist/components/compound/pipe-catalog/provider-filter.mjs.map +1 -1
- package/dist/components/compound/pipe-catalog/search-filter.mjs +1 -1
- package/dist/components/compound/pipe-catalog/search-filter.mjs.map +1 -1
- package/dist/components/compound/pipe-catalog/tag-filter.mjs +1 -1
- package/dist/components/compound/pipe-catalog/tag-filter.mjs.map +1 -1
- package/dist/components/compound/search-catalog/category-filter.mjs +1 -1
- package/dist/components/compound/search-catalog/category-filter.mjs.map +1 -1
- package/dist/components/compound/search-catalog/output-field-filter.mjs +1 -1
- package/dist/components/compound/search-catalog/output-field-filter.mjs.map +1 -1
- package/dist/components/compound/search-catalog/provider-filter.mjs +1 -1
- package/dist/components/compound/search-catalog/provider-filter.mjs.map +1 -1
- package/dist/components/compound/search-catalog/search-filter.mjs +1 -1
- package/dist/components/compound/search-catalog/search-filter.mjs.map +1 -1
- package/dist/components/compound/search-catalog/tag-filter.mjs +1 -1
- package/dist/components/compound/search-catalog/tag-filter.mjs.map +1 -1
- package/dist/components/defaults/adapters/json-extraction-input.mjs +1 -1
- package/dist/components/defaults/adapters/loose-object-input.mjs +1 -1
- package/dist/components/defaults/adapters/loose-object-input.mjs.map +1 -1
- package/dist/components/defaults/adapters/pipes-run-if-input.mjs +1 -1
- package/dist/components/defaults/adapters/prompt-input.mjs +1 -1
- package/dist/components/defaults/adapters/property-list-input.mjs +32 -16
- package/dist/components/defaults/adapters/property-list-input.mjs.map +1 -1
- package/dist/components/defaults/adapters/providers-input.mjs +1 -2
- package/dist/components/defaults/adapters/providers-input.mjs.map +1 -1
- package/dist/components/defaults/adapters/select-input.mjs +1 -1
- package/dist/components/defaults/adapters/select-input.mjs.map +1 -1
- package/dist/components/defaults/adapters/tagged-ordered-multi-create-input.mjs +2 -3
- package/dist/components/defaults/adapters/tagged-ordered-multi-create-input.mjs.map +1 -1
- package/dist/components/defaults/adapters/tagged-text-input.mjs +1 -1
- package/dist/components/defaults/adapters/tagged-text-input.mjs.map +1 -1
- package/dist/components/defaults/catalog/active-filter-pill.mjs +1 -1
- package/dist/components/defaults/catalog/active-filter-pill.mjs.map +1 -1
- package/dist/components/defaults/catalog/card-derived.d.mts.map +1 -1
- package/dist/components/defaults/catalog/card-derived.mjs +9 -6
- package/dist/components/defaults/catalog/card-derived.mjs.map +1 -1
- package/dist/components/defaults/catalog/category-section.d.mts +2 -2
- package/dist/components/defaults/catalog/category-section.d.mts.map +1 -1
- package/dist/components/defaults/catalog/category-section.mjs +10 -15
- package/dist/components/defaults/catalog/category-section.mjs.map +1 -1
- package/dist/components/defaults/catalog/provider-avatars.mjs +1 -1
- package/dist/components/defaults/catalog/provider-avatars.mjs.map +1 -1
- package/dist/components/defaults/layout/field-wrapper.mjs +2 -2
- package/dist/components/defaults/layout/field-wrapper.mjs.map +1 -1
- package/dist/components/defaults/layout/group.mjs +2 -1
- package/dist/components/defaults/layout/group.mjs.map +1 -1
- package/dist/components/defaults/layout/section.mjs +5 -8
- package/dist/components/defaults/layout/section.mjs.map +1 -1
- package/dist/components/internal/LiquidEditor/ChipEditPopover.mjs +1 -1
- package/dist/components/internal/LiquidEditor/ChipEditPopover.mjs.map +1 -1
- package/dist/components/internal/LiquidEditor/UnifiedReferencePicker.mjs +1 -1
- package/dist/components/internal/LiquidEditor/UnifiedReferencePicker.mjs.map +1 -1
- package/dist/components/internal/combobox/include-exclude-combobox.mjs +2 -3
- package/dist/components/internal/combobox/include-exclude-combobox.mjs.map +1 -1
- package/dist/components/internal/combobox/suggest-combobox.mjs +2 -3
- package/dist/components/internal/combobox/suggest-combobox.mjs.map +1 -1
- package/dist/components/internal/icons.d.mts +14 -0
- package/dist/components/internal/icons.d.mts.map +1 -0
- package/dist/components/internal/icons.mjs +137 -185
- package/dist/components/internal/icons.mjs.map +1 -1
- package/dist/components/ui/combobox.mjs +4 -4
- package/dist/components/ui/combobox.mjs.map +1 -1
- package/dist/components/ui/select.mjs +5 -5
- package/dist/components/ui/select.mjs.map +1 -1
- package/dist/hooks/use-pipe-catalog-table.d.mts +8 -8
- package/dist/hooks/use-search-catalog-table.d.mts +6 -6
- package/dist/styles/pipe0-form.css +1 -1
- package/dist/utils/catalog-helpers.d.mts +4 -5
- package/dist/utils/catalog-helpers.d.mts.map +1 -1
- package/dist/utils/catalog-helpers.mjs +4 -4
- package/dist/utils/catalog-helpers.mjs.map +1 -1
- package/dist/widgets/avatar-group.d.mts +12 -3
- package/dist/widgets/avatar-group.d.mts.map +1 -1
- package/dist/widgets/avatar-group.mjs +173 -50
- package/dist/widgets/avatar-group.mjs.map +1 -1
- package/dist/widgets/field-type-badge.d.mts.map +1 -1
- package/dist/widgets/field-type-badge.mjs +1 -1
- package/dist/widgets/field-type-badge.mjs.map +1 -1
- package/dist/widgets/icon-glyph.mjs +2 -3
- package/dist/widgets/icon-glyph.mjs.map +1 -1
- package/dist/widgets/token-pricing-badge.mjs +1 -1
- package/dist/widgets/token-pricing-badge.mjs.map +1 -1
- package/package.json +4 -3
- package/dist/widgets/glyphs/linkedin.mjs +0 -32
- package/dist/widgets/glyphs/linkedin.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers-input.mjs","names":["XIcon","SearchIcon"],"sources":["../../../../src/components/defaults/adapters/providers-input.tsx"],"sourcesContent":["import {\n closestCenter,\n DndContext,\n type DragEndEvent,\n KeyboardSensor,\n PointerSensor,\n useSensor,\n useSensors,\n} from \"@dnd-kit/core\";\nimport { SortableContext, useSortable, verticalListSortingStrategy } from \"@dnd-kit/sortable\";\nimport { type ProviderName, providerCatalog, type WidgetsByKind } from \"@pipe0/base\";\nimport { Search as SearchIcon, X as XIcon } from \"lucide-react\";\nimport { type CSSProperties, useMemo, useState } from \"react\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { FieldHandle } from \"../../../types/field-handle.js\";\nimport { AvatarGroup } from \"../../../widgets/avatar-group.js\";\nimport { PricingBadge } from \"../../../widgets/pricing-badge.js\";\nimport { WidgetStrip } from \"../../../widgets/widget-strip.js\";\nimport { IconGripVertical, IconPlus } from \"../../internal/icons.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../../ui/popover.js\";\n\ntype ProviderOption = {\n value: string;\n label: string;\n widgets?: WidgetsByKind;\n};\n\n// Stored payloads may reference providers that are no longer offered\n// (deprecated providers are excluded from the options but stay valid), so\n// resolve their display data from the provider catalog to keep the row\n// legible and removable.\nfunction resolveFallbackOption(value: string): ProviderOption {\n if (!(value in providerCatalog)) return { value, label: value };\n const provider = value as ProviderName;\n return {\n value,\n label: providerCatalog[provider].label,\n widgets: { provider_logo: { provider } },\n };\n}\n\nfunction splitWidgets(widgets: WidgetsByKind | undefined): {\n providerName: ProviderName | undefined;\n leading: WidgetsByKind | undefined;\n pricing: WidgetsByKind[\"pricing\"] | undefined;\n} {\n if (!widgets) return { providerName: undefined, leading: undefined, pricing: undefined };\n const { pricing, provider_logo, ...rest } = widgets;\n const hasLeading = Object.values(rest).some((v) => v != null);\n return {\n providerName: provider_logo?.provider,\n leading: hasLeading ? rest : undefined,\n pricing,\n };\n}\n\nfunction LeadingAvatar({\n providerName,\n leading,\n}: {\n providerName: ProviderName | undefined;\n leading: WidgetsByKind | undefined;\n}) {\n if (providerName) {\n return <AvatarGroup providers={[providerName]} size=\"sm\" className=\"pz:shrink-0\" />;\n }\n if (leading) {\n return (\n <span className=\"pz:flex pz:items-center pz:shrink-0\">\n <WidgetStrip widgets={leading} size={18} />\n </span>\n );\n }\n return null;\n}\n\n/**\n * Provider waterfall — vertical, ordered list of providers tried sequentially\n * until a valid result is returned. The list makes the running order legible\n * (numeric badge per row, drag-to-reorder), and the credit widget per\n * provider stays visible.\n */\nexport function ProvidersInputAdapter(field: FieldHandle<\"providers_input\">) {\n const selected: string[] = useMemo(\n () => (field.value ?? []).map((p: { provider: string }) => p.provider),\n [field.value],\n );\n\n const options = field.options ?? [];\n const optionByValue = useMemo(() => {\n const map = new Map<string, ProviderOption>();\n for (const o of options) map.set(o.value, o);\n return map;\n }, [options]);\n\n const maxItems = field.meta.maxItems;\n const atMax = maxItems !== undefined && selected.length >= maxItems;\n\n const setValue = (next: string[]) => field.setValue(next.map((provider) => ({ provider })));\n\n const handleRemove = (value: string) => setValue(selected.filter((v) => v !== value));\n const handleAdd = (value: string) => {\n if (selected.includes(value)) return;\n setValue([...selected, value]);\n };\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor),\n );\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const from = selected.indexOf(String(active.id));\n const to = selected.indexOf(String(over.id));\n if (from === -1 || to === -1) return;\n const next = [...selected];\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved);\n setValue(next);\n };\n\n return (\n <div data-p0=\"input\" className=\"pz:flex pz:flex-col pz:gap-1\">\n {selected.length > 0 && (\n <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>\n <SortableContext items={selected} strategy={verticalListSortingStrategy}>\n <ul className=\"pz:flex pz:flex-col pz:gap-0.5 pz:list-none pz:m-0 pz:p-0\">\n {selected.map((value, index) => {\n const option = optionByValue.get(value) ?? resolveFallbackOption(value);\n return (\n <ProviderRow\n key={value}\n value={value}\n index={index}\n label={option.label}\n widgets={option.widgets}\n onRemove={() => handleRemove(value)}\n />\n );\n })}\n </ul>\n </SortableContext>\n </DndContext>\n )}\n {!atMax && (\n <AddProviderControl\n options={options}\n excluded={selected}\n onAdd={handleAdd}\n ariaInvalid={!!field.error}\n />\n )}\n </div>\n );\n}\n\nfunction ProviderRow({\n value,\n index,\n label,\n widgets,\n onRemove,\n}: {\n value: string;\n index: number;\n label: string;\n widgets?: WidgetsByKind;\n onRemove: () => void;\n}) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id: value,\n });\n const style: CSSProperties = {\n transform: transform\n ? `translate3d(${Math.round(transform.x)}px, ${Math.round(transform.y)}px, 0)`\n : undefined,\n transition,\n opacity: isDragging ? 0.6 : 1,\n };\n\n const { providerName, leading, pricing } = splitWidgets(widgets);\n const orderLabel = String(index + 1);\n\n return (\n <li\n ref={setNodeRef}\n style={style}\n className={cn(\n \"pz:group/prov pz:flex pz:items-center pz:gap-2.5 pz:rounded-md pz:px-1.5 pz:py-1.5\",\n \"pz:hover:bg-muted/60 pz:transition-colors\",\n )}\n {...attributes}\n >\n <button\n type=\"button\"\n aria-label=\"Drag to reorder\"\n className={cn(\n \"pz:flex pz:items-center pz:justify-center pz:size-4 pz:cursor-grab pz:touch-none\",\n \"pz:text-muted-foreground/60 pz:hover:text-foreground pz:transition-colors\",\n )}\n {...listeners}\n >\n <IconGripVertical width={11} height={14} />\n </button>\n <LeadingAvatar providerName={providerName} leading={leading} />\n <span className=\"pz:text-sm pz:font-medium pz:text-foreground pz:flex-1 pz:truncate\">\n {label}\n </span>\n {pricing && <PricingBadge credits={pricing.credits} />}\n <span\n aria-hidden\n className={cn(\n \"pz:inline-flex pz:items-center pz:justify-center pz:min-w-7 pz:h-5 pz:px-1.5\",\n \"pz:rounded-full pz:bg-muted pz:text-muted-foreground pz:text-[10.5px]\",\n \"pz:font-medium pz:tabular-nums\",\n )}\n >\n {orderLabel}\n </span>\n <button\n type=\"button\"\n aria-label={`Remove ${label}`}\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n className={cn(\n \"pz:flex pz:items-center pz:justify-center pz:size-5 pz:rounded\",\n \"pz:text-muted-foreground/70\",\n \"pz:hover:bg-destructive/10 pz:hover:text-destructive pz:transition-colors\",\n )}\n >\n <XIcon className=\"pz:size-3\" />\n </button>\n </li>\n );\n}\n\nfunction AddProviderControl({\n options,\n excluded,\n onAdd,\n ariaInvalid,\n}: {\n options: ProviderOption[];\n excluded: string[];\n onAdd: (value: string) => void;\n ariaInvalid?: boolean;\n}) {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n\n const excludedSet = useMemo(() => new Set(excluded), [excluded]);\n const available = useMemo(\n () => options.filter((o) => !excludedSet.has(o.value)),\n [options, excludedSet],\n );\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return available;\n return available.filter(\n (o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q),\n );\n }, [available, query]);\n\n if (available.length === 0 && excluded.length === 0) {\n return (\n <div className=\"pz:text-xs pz:text-muted-foreground pz:px-1.5 pz:py-2\">\n No providers available\n </div>\n );\n }\n\n if (available.length === 0) return null;\n\n return (\n <Popover\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) setQuery(\"\");\n }}\n >\n <PopoverTrigger\n aria-invalid={ariaInvalid || undefined}\n className={cn(\n \"pz:flex pz:items-center pz:justify-center pz:gap-1.5 pz:w-full\",\n \"pz:px-2.5 pz:py-1.5 pz:mt-1 pz:rounded-md\",\n \"pz:border pz:border-dashed pz:border-input pz:bg-transparent\",\n \"pz:text-xs pz:text-muted-foreground\",\n \"pz:hover:bg-muted/60 pz:hover:text-foreground pz:hover:border-muted-foreground/40\",\n \"pz:transition-colors pz:cursor-pointer\",\n )}\n >\n <IconPlus width={12} height={12} />\n Add provider\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"pz:w-(--anchor-width) pz:p-0 pz:gap-0\">\n <div className=\"pz:flex pz:items-center pz:gap-1.5 pz:px-2.5 pz:py-2 pz:border-b pz:border-border\">\n <SearchIcon className=\"pz:size-3.5 pz:text-muted-foreground pz:shrink-0\" />\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search providers…\"\n // biome-ignore lint/a11y/noAutofocus: popover-scoped picker\n autoFocus\n className=\"pz:flex-1 pz:bg-transparent pz:outline-none pz:text-sm pz:placeholder:text-muted-foreground/70\"\n />\n </div>\n <ul className=\"pz:flex pz:flex-col pz:p-1 pz:max-h-72 pz:overflow-auto pz:list-none pz:m-0\">\n {filtered.length === 0 && (\n <li className=\"pz:px-2 pz:py-3 pz:text-center pz:text-xs pz:text-muted-foreground\">\n No providers\n </li>\n )}\n {filtered.map((option) => {\n const { providerName, leading, pricing } = splitWidgets(option.widgets);\n return (\n <li key={option.value} className=\"pz:contents\">\n <button\n type=\"button\"\n onClick={() => {\n onAdd(option.value);\n setOpen(false);\n setQuery(\"\");\n }}\n className={cn(\n \"pz:flex pz:items-center pz:gap-2 pz:w-full pz:text-left\",\n \"pz:px-2 pz:py-1.5 pz:rounded-md pz:text-sm\",\n \"pz:hover:bg-accent pz:hover:text-accent-foreground\",\n \"pz:transition-colors pz:cursor-pointer\",\n )}\n >\n <LeadingAvatar providerName={providerName} leading={leading} />\n <span className=\"pz:flex-1 pz:truncate\">{option.label}</span>\n {pricing && <PricingBadge credits={pricing.credits} />}\n </button>\n </li>\n );\n })}\n </ul>\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AA+BA,SAAS,sBAAsB,OAA+B;AAC5D,KAAI,EAAE,SAAS,iBAAkB,QAAO;EAAE;EAAO,OAAO;EAAO;CAC/D,MAAM,WAAW;AACjB,QAAO;EACL;EACA,OAAO,gBAAgB,UAAU;EACjC,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE;EACzC;;AAGH,SAAS,aAAa,SAIpB;AACA,KAAI,CAAC,QAAS,QAAO;EAAE,cAAc;EAAW,SAAS;EAAW,SAAS;EAAW;CACxF,MAAM,EAAE,SAAS,eAAe,GAAG,SAAS;CAC5C,MAAM,aAAa,OAAO,OAAO,KAAK,CAAC,MAAM,MAAM,KAAK,KAAK;AAC7D,QAAO;EACL,cAAc,eAAe;EAC7B,SAAS,aAAa,OAAO;EAC7B;EACD;;AAGH,SAAS,cAAc,EACrB,cACA,WAIC;AACD,KAAI,aACF,QAAO,oBAAC,aAAD;EAAa,WAAW,CAAC,aAAa;EAAE,MAAK;EAAK,WAAU;EAAgB;AAErF,KAAI,QACF,QACE,oBAAC,QAAD;EAAM,WAAU;YACd,oBAAC,aAAD;GAAa,SAAS;GAAS,MAAM;GAAM;EACtC;AAGX,QAAO;;;;;;;;AAST,SAAgB,sBAAsB,OAAuC;CAC3E,MAAM,WAAqB,eAClB,MAAM,SAAS,EAAE,EAAE,KAAK,MAA4B,EAAE,SAAS,EACtE,CAAC,MAAM,MAAM,CACd;CAED,MAAM,UAAU,MAAM,WAAW,EAAE;CACnC,MAAM,gBAAgB,cAAc;EAClC,MAAM,sBAAM,IAAI,KAA6B;AAC7C,OAAK,MAAM,KAAK,QAAS,KAAI,IAAI,EAAE,OAAO,EAAE;AAC5C,SAAO;IACN,CAAC,QAAQ,CAAC;CAEb,MAAM,WAAW,MAAM,KAAK;CAC5B,MAAM,QAAQ,aAAa,UAAa,SAAS,UAAU;CAE3D,MAAM,YAAY,SAAmB,MAAM,SAAS,KAAK,KAAK,cAAc,EAAE,UAAU,EAAE,CAAC;CAE3F,MAAM,gBAAgB,UAAkB,SAAS,SAAS,QAAQ,MAAM,MAAM,MAAM,CAAC;CACrF,MAAM,aAAa,UAAkB;AACnC,MAAI,SAAS,SAAS,MAAM,CAAE;AAC9B,WAAS,CAAC,GAAG,UAAU,MAAM,CAAC;;CAGhC,MAAM,UAAU,WACd,UAAU,eAAe,EAAE,sBAAsB,EAAE,UAAU,GAAG,EAAE,CAAC,EACnE,UAAU,eAAe,CAC1B;CAED,MAAM,iBAAiB,UAAwB;EAC7C,MAAM,EAAE,QAAQ,SAAS;AACzB,MAAI,CAAC,QAAQ,OAAO,OAAO,KAAK,GAAI;EACpC,MAAM,OAAO,SAAS,QAAQ,OAAO,OAAO,GAAG,CAAC;EAChD,MAAM,KAAK,SAAS,QAAQ,OAAO,KAAK,GAAG,CAAC;AAC5C,MAAI,SAAS,MAAM,OAAO,GAAI;EAC9B,MAAM,OAAO,CAAC,GAAG,SAAS;EAC1B,MAAM,CAAC,SAAS,KAAK,OAAO,MAAM,EAAE;AACpC,OAAK,OAAO,IAAI,GAAG,MAAM;AACzB,WAAS,KAAK;;AAGhB,QACE,qBAAC,OAAD;EAAK,WAAQ;EAAQ,WAAU;YAA/B,CACG,SAAS,SAAS,KACjB,oBAAC,YAAD;GAAqB;GAAS,oBAAoB;GAAe,WAAW;aAC1E,oBAAC,iBAAD;IAAiB,OAAO;IAAU,UAAU;cAC1C,oBAAC,MAAD;KAAI,WAAU;eACX,SAAS,KAAK,OAAO,UAAU;MAC9B,MAAM,SAAS,cAAc,IAAI,MAAM,IAAI,sBAAsB,MAAM;AACvE,aACE,oBAAC,aAAD;OAES;OACA;OACP,OAAO,OAAO;OACd,SAAS,OAAO;OAChB,gBAAgB,aAAa,MAAM;OACnC,EANK,MAML;OAEJ;KACC;IACW;GACP,GAEd,CAAC,SACA,oBAAC,oBAAD;GACW;GACT,UAAU;GACV,OAAO;GACP,aAAa,CAAC,CAAC,MAAM;GACrB,EAEA;;;AAIV,SAAS,YAAY,EACnB,OACA,OACA,OACA,SACA,YAOC;CACD,MAAM,EAAE,YAAY,WAAW,YAAY,WAAW,YAAY,eAAe,YAAY,EAC3F,IAAI,OACL,CAAC;CACF,MAAM,QAAuB;EAC3B,WAAW,YACP,eAAe,KAAK,MAAM,UAAU,EAAE,CAAC,MAAM,KAAK,MAAM,UAAU,EAAE,CAAC,UACrE;EACJ;EACA,SAAS,aAAa,KAAM;EAC7B;CAED,MAAM,EAAE,cAAc,SAAS,YAAY,aAAa,QAAQ;CAChE,MAAM,aAAa,OAAO,QAAQ,EAAE;AAEpC,QACE,qBAAC,MAAD;EACE,KAAK;EACE;EACP,WAAW,GACT,sFACA,4CACD;EACD,GAAI;YAPN;GASE,oBAAC,UAAD;IACE,MAAK;IACL,cAAW;IACX,WAAW,GACT,oFACA,4EACD;IACD,GAAI;cAEJ,oBAAC,kBAAD;KAAkB,OAAO;KAAI,QAAQ;KAAM;IACpC;GACT,oBAAC,eAAD;IAA6B;IAAuB;IAAW;GAC/D,oBAAC,QAAD;IAAM,WAAU;cACb;IACI;GACN,WAAW,oBAAC,cAAD,EAAc,SAAS,QAAQ,SAAW;GACtD,oBAAC,QAAD;IACE;IACA,WAAW,GACT,gFACA,yEACA,iCACD;cAEA;IACI;GACP,oBAAC,UAAD;IACE,MAAK;IACL,cAAY,UAAU;IACtB,UAAU,MAAM;AACd,OAAE,iBAAiB;AACnB,eAAU;;IAEZ,WAAW,GACT,kEACA,+BACA,4EACD;cAED,oBAACA,GAAD,EAAO,WAAU,aAAc;IACxB;GACN;;;AAIT,SAAS,mBAAmB,EAC1B,SACA,UACA,OACA,eAMC;CACD,MAAM,CAAC,MAAM,WAAW,SAAS,MAAM;CACvC,MAAM,CAAC,OAAO,YAAY,SAAS,GAAG;CAEtC,MAAM,cAAc,cAAc,IAAI,IAAI,SAAS,EAAE,CAAC,SAAS,CAAC;CAChE,MAAM,YAAY,cACV,QAAQ,QAAQ,MAAM,CAAC,YAAY,IAAI,EAAE,MAAM,CAAC,EACtD,CAAC,SAAS,YAAY,CACvB;CAED,MAAM,WAAW,cAAc;EAC7B,MAAM,IAAI,MAAM,MAAM,CAAC,aAAa;AACpC,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,UAAU,QACd,MAAM,EAAE,MAAM,aAAa,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC,SAAS,EAAE,CAC9E;IACA,CAAC,WAAW,MAAM,CAAC;AAEtB,KAAI,UAAU,WAAW,KAAK,SAAS,WAAW,EAChD,QACE,oBAAC,OAAD;EAAK,WAAU;YAAwD;EAEjE;AAIV,KAAI,UAAU,WAAW,EAAG,QAAO;AAEnC,QACE,qBAAC,SAAD;EACQ;EACN,eAAe,SAAS;AACtB,WAAQ,KAAK;AACb,OAAI,CAAC,KAAM,UAAS,GAAG;;YAJ3B,CAOE,qBAAC,gBAAD;GACE,gBAAc,eAAe;GAC7B,WAAW,GACT,kEACA,6CACA,gEACA,uCACA,qFACA,yCACD;aATH,CAWE,oBAAC,UAAD;IAAU,OAAO;IAAI,QAAQ;IAAM,kBAEpB;MACjB,qBAAC,gBAAD;GAAgB,OAAM;GAAQ,WAAU;aAAxC,CACE,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAACC,QAAD,EAAY,WAAU,oDAAqD,GAC3E,oBAAC,SAAD;KACE,OAAO;KACP,WAAW,MAAM,SAAS,EAAE,OAAO,MAAM;KACzC,aAAY;KAEZ;KACA,WAAU;KACV,EACE;OACN,qBAAC,MAAD;IAAI,WAAU;cAAd,CACG,SAAS,WAAW,KACnB,oBAAC,MAAD;KAAI,WAAU;eAAqE;KAE9E,GAEN,SAAS,KAAK,WAAW;KACxB,MAAM,EAAE,cAAc,SAAS,YAAY,aAAa,OAAO,QAAQ;AACvE,YACE,oBAAC,MAAD;MAAuB,WAAU;gBAC/B,qBAAC,UAAD;OACE,MAAK;OACL,eAAe;AACb,cAAM,OAAO,MAAM;AACnB,gBAAQ,MAAM;AACd,iBAAS,GAAG;;OAEd,WAAW,GACT,2DACA,8CACA,sDACA,yCACD;iBAZH;QAcE,oBAAC,eAAD;SAA6B;SAAuB;SAAW;QAC/D,oBAAC,QAAD;SAAM,WAAU;mBAAyB,OAAO;SAAa;QAC5D,WAAW,oBAAC,cAAD,EAAc,SAAS,QAAQ,SAAW;QAC/C;;MACN,EAnBI,OAAO,MAmBX;MAEP,CACC;MACU;KACT"}
|
|
1
|
+
{"version":3,"file":"providers-input.mjs","names":["XIcon","SearchIcon"],"sources":["../../../../src/components/defaults/adapters/providers-input.tsx"],"sourcesContent":["import {\n closestCenter,\n DndContext,\n type DragEndEvent,\n KeyboardSensor,\n PointerSensor,\n useSensor,\n useSensors,\n} from \"@dnd-kit/core\";\nimport { SortableContext, useSortable, verticalListSortingStrategy } from \"@dnd-kit/sortable\";\nimport { type ProviderName, providerCatalog, type WidgetsByKind } from \"@pipe0/base\";\nimport { type CSSProperties, useMemo, useState } from \"react\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { FieldHandle } from \"../../../types/field-handle.js\";\nimport { AvatarGroup } from \"../../../widgets/avatar-group.js\";\nimport { PricingBadge } from \"../../../widgets/pricing-badge.js\";\nimport { WidgetStrip } from \"../../../widgets/widget-strip.js\";\nimport {\n IconGripVertical,\n IconPlus,\n Search as SearchIcon,\n X as XIcon,\n} from \"../../internal/icons.js\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../../ui/popover.js\";\n\ntype ProviderOption = {\n value: string;\n label: string;\n widgets?: WidgetsByKind;\n};\n\n// Stored payloads may reference providers that are no longer offered\n// (deprecated providers are excluded from the options but stay valid), so\n// resolve their display data from the provider catalog to keep the row\n// legible and removable.\nfunction resolveFallbackOption(value: string): ProviderOption {\n if (!(value in providerCatalog)) return { value, label: value };\n const provider = value as ProviderName;\n return {\n value,\n label: providerCatalog[provider].label,\n widgets: { provider_logo: { provider } },\n };\n}\n\nfunction splitWidgets(widgets: WidgetsByKind | undefined): {\n providerName: ProviderName | undefined;\n leading: WidgetsByKind | undefined;\n pricing: WidgetsByKind[\"pricing\"] | undefined;\n} {\n if (!widgets) return { providerName: undefined, leading: undefined, pricing: undefined };\n const { pricing, provider_logo, ...rest } = widgets;\n const hasLeading = Object.values(rest).some((v) => v != null);\n return {\n providerName: provider_logo?.provider,\n leading: hasLeading ? rest : undefined,\n pricing,\n };\n}\n\nfunction LeadingAvatar({\n providerName,\n leading,\n}: {\n providerName: ProviderName | undefined;\n leading: WidgetsByKind | undefined;\n}) {\n if (providerName) {\n return <AvatarGroup providers={[providerName]} size=\"sm\" className=\"pz:shrink-0\" />;\n }\n if (leading) {\n return (\n <span className=\"pz:flex pz:items-center pz:shrink-0\">\n <WidgetStrip widgets={leading} size={18} />\n </span>\n );\n }\n return null;\n}\n\n/**\n * Provider waterfall — vertical, ordered list of providers tried sequentially\n * until a valid result is returned. The list makes the running order legible\n * (numeric badge per row, drag-to-reorder), and the credit widget per\n * provider stays visible.\n */\nexport function ProvidersInputAdapter(field: FieldHandle<\"providers_input\">) {\n const selected: string[] = useMemo(\n () => (field.value ?? []).map((p: { provider: string }) => p.provider),\n [field.value],\n );\n\n const options = field.options ?? [];\n const optionByValue = useMemo(() => {\n const map = new Map<string, ProviderOption>();\n for (const o of options) map.set(o.value, o);\n return map;\n }, [options]);\n\n const maxItems = field.meta.maxItems;\n const atMax = maxItems !== undefined && selected.length >= maxItems;\n\n const setValue = (next: string[]) => field.setValue(next.map((provider) => ({ provider })));\n\n const handleRemove = (value: string) => setValue(selected.filter((v) => v !== value));\n const handleAdd = (value: string) => {\n if (selected.includes(value)) return;\n setValue([...selected, value]);\n };\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor),\n );\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const from = selected.indexOf(String(active.id));\n const to = selected.indexOf(String(over.id));\n if (from === -1 || to === -1) return;\n const next = [...selected];\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved);\n setValue(next);\n };\n\n return (\n <div data-p0=\"input\" className=\"pz:flex pz:flex-col pz:gap-1\">\n {selected.length > 0 && (\n <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>\n <SortableContext items={selected} strategy={verticalListSortingStrategy}>\n <ul className=\"pz:flex pz:flex-col pz:gap-0.5 pz:list-none pz:m-0 pz:p-0\">\n {selected.map((value, index) => {\n const option = optionByValue.get(value) ?? resolveFallbackOption(value);\n return (\n <ProviderRow\n key={value}\n value={value}\n index={index}\n label={option.label}\n widgets={option.widgets}\n onRemove={() => handleRemove(value)}\n />\n );\n })}\n </ul>\n </SortableContext>\n </DndContext>\n )}\n {!atMax && (\n <AddProviderControl\n options={options}\n excluded={selected}\n onAdd={handleAdd}\n ariaInvalid={!!field.error}\n />\n )}\n </div>\n );\n}\n\nfunction ProviderRow({\n value,\n index,\n label,\n widgets,\n onRemove,\n}: {\n value: string;\n index: number;\n label: string;\n widgets?: WidgetsByKind;\n onRemove: () => void;\n}) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id: value,\n });\n const style: CSSProperties = {\n transform: transform\n ? `translate3d(${Math.round(transform.x)}px, ${Math.round(transform.y)}px, 0)`\n : undefined,\n transition,\n opacity: isDragging ? 0.6 : 1,\n };\n\n const { providerName, leading, pricing } = splitWidgets(widgets);\n const orderLabel = String(index + 1);\n\n return (\n <li\n ref={setNodeRef}\n style={style}\n className={cn(\n \"pz:group/prov pz:flex pz:items-center pz:gap-2.5 pz:rounded-md pz:px-1.5 pz:py-1.5\",\n \"pz:hover:bg-muted/60 pz:transition-colors\",\n )}\n {...attributes}\n >\n <button\n type=\"button\"\n aria-label=\"Drag to reorder\"\n className={cn(\n \"pz:flex pz:items-center pz:justify-center pz:size-4 pz:cursor-grab pz:touch-none\",\n \"pz:text-muted-foreground/60 pz:hover:text-foreground pz:transition-colors\",\n )}\n {...listeners}\n >\n <IconGripVertical width={11} height={14} />\n </button>\n <LeadingAvatar providerName={providerName} leading={leading} />\n <span className=\"pz:text-sm pz:font-medium pz:text-foreground pz:flex-1 pz:truncate\">\n {label}\n </span>\n {pricing && <PricingBadge credits={pricing.credits} />}\n <span\n aria-hidden\n className={cn(\n \"pz:inline-flex pz:items-center pz:justify-center pz:min-w-7 pz:h-5 pz:px-1.5\",\n \"pz:rounded-full pz:bg-muted pz:text-muted-foreground pz:text-[10.5px]\",\n \"pz:font-medium pz:tabular-nums\",\n )}\n >\n {orderLabel}\n </span>\n <button\n type=\"button\"\n aria-label={`Remove ${label}`}\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n className={cn(\n \"pz:flex pz:items-center pz:justify-center pz:size-5 pz:rounded\",\n \"pz:text-muted-foreground/70\",\n \"pz:hover:bg-destructive/10 pz:hover:text-destructive pz:transition-colors\",\n )}\n >\n <XIcon className=\"pz:size-3\" />\n </button>\n </li>\n );\n}\n\nfunction AddProviderControl({\n options,\n excluded,\n onAdd,\n ariaInvalid,\n}: {\n options: ProviderOption[];\n excluded: string[];\n onAdd: (value: string) => void;\n ariaInvalid?: boolean;\n}) {\n const [open, setOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n\n const excludedSet = useMemo(() => new Set(excluded), [excluded]);\n const available = useMemo(\n () => options.filter((o) => !excludedSet.has(o.value)),\n [options, excludedSet],\n );\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return available;\n return available.filter(\n (o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q),\n );\n }, [available, query]);\n\n if (available.length === 0 && excluded.length === 0) {\n return (\n <div className=\"pz:text-xs pz:text-muted-foreground pz:px-1.5 pz:py-2\">\n No providers available\n </div>\n );\n }\n\n if (available.length === 0) return null;\n\n return (\n <Popover\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (!next) setQuery(\"\");\n }}\n >\n <PopoverTrigger\n aria-invalid={ariaInvalid || undefined}\n className={cn(\n \"pz:flex pz:items-center pz:justify-center pz:gap-1.5 pz:w-full\",\n \"pz:px-2.5 pz:py-1.5 pz:mt-1 pz:rounded-md\",\n \"pz:border pz:border-dashed pz:border-input pz:bg-transparent\",\n \"pz:text-xs pz:text-muted-foreground\",\n \"pz:hover:bg-muted/60 pz:hover:text-foreground pz:hover:border-muted-foreground/40\",\n \"pz:transition-colors pz:cursor-pointer\",\n )}\n >\n <IconPlus width={12} height={12} />\n Add provider\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"pz:w-(--anchor-width) pz:p-0 pz:gap-0\">\n <div className=\"pz:flex pz:items-center pz:gap-1.5 pz:px-2.5 pz:py-2 pz:border-b pz:border-border\">\n <SearchIcon className=\"pz:size-3.5 pz:text-muted-foreground pz:shrink-0\" />\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search providers…\"\n // biome-ignore lint/a11y/noAutofocus: popover-scoped picker\n autoFocus\n className=\"pz:flex-1 pz:bg-transparent pz:outline-none pz:text-sm pz:placeholder:text-muted-foreground/70\"\n />\n </div>\n <ul className=\"pz:flex pz:flex-col pz:p-1 pz:max-h-72 pz:overflow-auto pz:list-none pz:m-0\">\n {filtered.length === 0 && (\n <li className=\"pz:px-2 pz:py-3 pz:text-center pz:text-xs pz:text-muted-foreground\">\n No providers\n </li>\n )}\n {filtered.map((option) => {\n const { providerName, leading, pricing } = splitWidgets(option.widgets);\n return (\n <li key={option.value} className=\"pz:contents\">\n <button\n type=\"button\"\n onClick={() => {\n onAdd(option.value);\n setOpen(false);\n setQuery(\"\");\n }}\n className={cn(\n \"pz:flex pz:items-center pz:gap-2 pz:w-full pz:text-left\",\n \"pz:px-2 pz:py-1.5 pz:rounded-md pz:text-sm\",\n \"pz:hover:bg-accent pz:hover:text-accent-foreground\",\n \"pz:transition-colors pz:cursor-pointer\",\n )}\n >\n <LeadingAvatar providerName={providerName} leading={leading} />\n <span className=\"pz:flex-1 pz:truncate\">{option.label}</span>\n {pricing && <PricingBadge credits={pricing.credits} />}\n </button>\n </li>\n );\n })}\n </ul>\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { X } from "../../internal/icons.mjs";
|
|
1
2
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../../ui/select.mjs";
|
|
2
3
|
import { WidgetStrip } from "../../../widgets/widget-strip.mjs";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { X } from "lucide-react";
|
|
5
5
|
|
|
6
6
|
//#region src/components/defaults/adapters/select-input.tsx
|
|
7
7
|
function OptionContent({ label, widgets }) {
|
|
@@ -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 {
|
|
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 { X } from \"../../internal/icons.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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.mjs";
|
|
2
2
|
import { Button } from "../../ui/button.mjs";
|
|
3
|
-
import { IconGripVertical } from "../../internal/icons.mjs";
|
|
3
|
+
import { IconGripVertical, X } from "../../internal/icons.mjs";
|
|
4
4
|
import { useDebouncedFn } from "../../../hooks/use-debounced-fn.mjs";
|
|
5
5
|
import { FieldTypeBadge } from "../../../widgets/field-type-badge.mjs";
|
|
6
6
|
import { WidgetStrip } from "../../../widgets/widget-strip.mjs";
|
|
@@ -8,7 +8,6 @@ import { Combobox, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxE
|
|
|
8
8
|
import { filterOptions } from "../../internal/multi-select-popover-trigger.mjs";
|
|
9
9
|
import { useId, useMemo, useRef, useState } from "react";
|
|
10
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
import { XIcon } from "lucide-react";
|
|
12
11
|
import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
13
12
|
import { SortableContext, horizontalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
|
|
14
13
|
import useSWR from "swr";
|
|
@@ -332,7 +331,7 @@ function ChipShell({ setNodeRef, style, attributes, listeners, kind, onRemove, c
|
|
|
332
331
|
onRemove();
|
|
333
332
|
},
|
|
334
333
|
"aria-label": "Remove",
|
|
335
|
-
children: /* @__PURE__ */ jsx(
|
|
334
|
+
children: /* @__PURE__ */ jsx(X, { className: "pz:pointer-events-none" })
|
|
336
335
|
})
|
|
337
336
|
]
|
|
338
337
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tagged-ordered-multi-create-input.mjs","names":[],"sources":["../../../../src/components/defaults/adapters/tagged-ordered-multi-create-input.tsx"],"sourcesContent":["import {\n closestCenter,\n DndContext,\n type DragEndEvent,\n KeyboardSensor,\n PointerSensor,\n useSensor,\n useSensors,\n} from \"@dnd-kit/core\";\nimport { horizontalListSortingStrategy, SortableContext, useSortable } from \"@dnd-kit/sortable\";\nimport type { PipesFieldDefinitionWithName, WidgetsByKind } from \"@pipe0/base\";\nimport { XIcon } from \"lucide-react\";\nimport {\n type CSSProperties,\n type ReactNode,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport useSWR from \"swr\";\nimport { useDebouncedFn } from \"../../../hooks/use-debounced-fn.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { FieldHandle } from \"../../../types/field-handle.js\";\nimport { FieldTypeBadge } from \"../../../widgets/field-type-badge.js\";\nimport { WidgetStrip } from \"../../../widgets/widget-strip.js\";\nimport { IconGripVertical } from \"../../internal/icons.js\";\nimport { filterOptions, type PayloadOption } from \"../../internal/multi-select-popover-trigger.js\";\nimport { Button } from \"../../ui/button.js\";\nimport {\n Combobox,\n ComboboxChips,\n ComboboxChipsInput,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxGroup,\n ComboboxItem,\n ComboboxLabel,\n ComboboxList,\n ComboboxTrigger,\n useComboboxAnchor,\n} from \"../../ui/combobox.js\";\n\n/**\n * Detects whether a raw value is a single, exclusive Liquid field reference\n * (e.g. `\"{{ company_domain }}\"` or `\"{{ field | filter }}\"`). Mixed content\n * such as `\"Head of {{ company }}\"` returns false and is treated as text.\n */\nconst PURE_TAG_RE = /^\\s*\\{\\{\\s*([a-zA-Z_][a-zA-Z0-9_.]*)(?:\\s*\\|\\s*[^}]+)?\\s*\\}\\}\\s*$/;\n\ntype ParsedChip =\n | { kind: \"field\"; fieldName: string; raw: string }\n | { kind: \"text\"; text: string; raw: string };\n\nfunction parseChip(raw: string): ParsedChip {\n const m = raw.match(PURE_TAG_RE);\n if (m) return { kind: \"field\", fieldName: m[1], raw };\n return { kind: \"text\", text: raw, raw };\n}\n\n/**\n * Sky-blue pill matching `TagChipDecoration`'s field chip styling so a\n * field-reference chip outside tiptap reads identically to one inside\n * `LiquidEditor`.\n */\nconst FIELD_CHIP_CLASS =\n \"pz:flex pz:h-[calc(--spacing(5.25))] pz:w-fit pz:items-center pz:gap-1 pz:rounded-sm pz:bg-sky-100 pz:pl-0.5 pz:pr-0 pz:text-xs pz:font-medium pz:whitespace-nowrap pz:text-sky-900\";\n\nconst TEXT_CHIP_CLASS =\n \"pz:flex pz:h-[calc(--spacing(5.25))] pz:w-fit pz:items-center pz:gap-1 pz:rounded-sm pz:bg-muted pz:pl-0.5 pz:pr-0 pz:text-xs pz:font-medium pz:whitespace-nowrap pz:text-foreground\";\n\n/** Drop target for an item carried by the Combobox value channel. */\ntype Item = {\n /** Canonical value to store in the field — text as-is, or `{{ fieldName }}`. */\n value: string;\n /** Display label in the dropdown. */\n label: string;\n kind: \"field\" | \"text\" | \"create\";\n widgets?: WidgetsByKind;\n};\n\nexport function TaggedOrderedMultiCreateInputAdapter(\n field: FieldHandle<\"tagged_ordered_multi_create_input\">,\n) {\n const { items, setItems, options, loadOptions, inputFields, meta } = field;\n const maxItems = meta.maxItems;\n const placeholder = meta.placeholder;\n\n const [input, setInput] = useState(\"\");\n const [debouncedQuery, setDebouncedQuery] = useState(\"\");\n const [open, setOpen] = useState(false);\n\n const isAsync = typeof loadOptions === \"function\";\n const debouncedSetQuery = useDebouncedFn((q: string) => setDebouncedQuery(q), 300);\n\n // Async suggestions — same SWR pattern as `SuggestCombobox`.\n const instanceId = useId();\n const swrKey: readonly [string, string] | null =\n loadOptions && (open || debouncedQuery !== \"\") ? [instanceId, debouncedQuery] : null;\n const {\n data: asyncResults = null,\n isLoading: swrIsLoading,\n isValidating: swrIsValidating,\n } = useSWR<PayloadOption[], Error, readonly [string, string] | null>(\n swrKey,\n async ([, q]) => {\n if (!loadOptions) return [];\n const opts = await loadOptions(q);\n return opts.filter((o) => !!o.value);\n },\n { keepPreviousData: true },\n );\n const loading = swrIsLoading || swrIsValidating;\n\n const trimmedInput = input.trim();\n\n const fieldRefItems = useMemo<Item[]>(() => {\n if (!inputFields?.length) return [];\n const q = trimmedInput.toLowerCase();\n return inputFields\n .filter((f) => !q || f.resolvedName.toLowerCase().includes(q))\n .map<Item>((f) => ({\n value: `{{ ${f.resolvedName} }}`,\n label: f.resolvedName,\n kind: \"field\",\n }));\n }, [inputFields, trimmedInput]);\n\n const suggestionItems = useMemo<Item[]>(() => {\n const source: PayloadOption[] = isAsync ? (asyncResults ?? []) : (options ?? []);\n const ranked: PayloadOption[] = isAsync ? source : filterOptions(source, input);\n return ranked.map<Item>((o) => ({\n value: o.value,\n label: o.label,\n kind: \"text\",\n widgets: o.widgets,\n }));\n }, [isAsync, asyncResults, options, input]);\n\n const matchesExistingSuggestion = useMemo(\n () =>\n suggestionItems.some((o) => o.value.toLowerCase() === trimmedInput.toLowerCase()) ||\n fieldRefItems.some((f) => f.value.toLowerCase() === trimmedInput.toLowerCase()),\n [suggestionItems, fieldRefItems, trimmedInput],\n );\n\n const showCreateRow = trimmedInput.length > 0 && !matchesExistingSuggestion;\n\n // Flat items list — fed to base-ui as the universe of pickable items so its\n // keyboard navigation has a single ordering to work with. The visual\n // grouping is done by `ComboboxGroup` in the render below; base-ui ranges\n // over `items` linearly and matches by reference.\n const allItems = useMemo<Item[]>(() => {\n const list: Item[] = [];\n for (const f of fieldRefItems) list.push(f);\n for (const s of suggestionItems) list.push(s);\n if (showCreateRow) {\n list.push({\n value: trimmedInput,\n label: `Add \"${trimmedInput}\"`,\n kind: \"create\",\n });\n }\n return list;\n }, [fieldRefItems, suggestionItems, showCreateRow, trimmedInput]);\n\n const atMax = maxItems !== undefined && items.length >= maxItems;\n\n // Stable per-position ids so `@dnd-kit` keys survive even when two chips\n // share the same raw string (uncommon, but allowed — the user could insert\n // the same field reference twice in a waterfall).\n const idsRef = useRef<string[]>([]);\n if (idsRef.current.length !== items.length) {\n if (idsRef.current.length < items.length) {\n idsRef.current = [\n ...idsRef.current,\n ...Array.from({ length: items.length - idsRef.current.length }, () => crypto.randomUUID()),\n ];\n } else {\n idsRef.current = idsRef.current.slice(0, items.length);\n }\n }\n const chipIds = idsRef.current;\n\n const sortSensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor),\n );\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const from = chipIds.indexOf(String(active.id));\n const to = chipIds.indexOf(String(over.id));\n if (from === -1 || to === -1) return;\n const nextItems = [...items];\n const [movedValue] = nextItems.splice(from, 1);\n nextItems.splice(to, 0, movedValue);\n const nextIds = [...chipIds];\n const [movedId] = nextIds.splice(from, 1);\n nextIds.splice(to, 0, movedId);\n idsRef.current = nextIds;\n setItems(nextItems);\n };\n\n const removeAt = (index: number) => {\n idsRef.current = idsRef.current.filter((_, i) => i !== index);\n setItems(items.filter((_, i) => i !== index));\n };\n\n const appendValue = (value: string) => {\n if (atMax || !value) return;\n idsRef.current = [...idsRef.current, crypto.randomUUID()];\n setItems([...items, value]);\n };\n\n const clearInput = () => {\n setInput(\"\");\n if (isAsync) debouncedSetQuery(\"\");\n };\n\n const anchor = useComboboxAnchor();\n\n return (\n <div data-p0=\"input\" className=\"pz:flex pz:flex-col pz:gap-1\">\n <Combobox<Item, true>\n multiple\n items={allItems}\n filter={null}\n open={open}\n onOpenChange={setOpen}\n // Chips are rendered manually so base-ui doesn't manage them — keep\n // `value` empty and capture picks via `onValueChange`. Same trick as\n // `IncludeExcludeCombobox`.\n value={[] as Item[]}\n onValueChange={(next) => {\n if (!Array.isArray(next) || next.length === 0) return;\n const last = next[next.length - 1] as Item;\n appendValue(last.value);\n clearInput();\n }}\n isItemEqualToValue={(a, b) => (a as Item).value === (b as Item).value}\n itemToStringLabel={(item) => (item as Item).label}\n itemToStringValue={(item) => (item as Item).value}\n inputValue={input}\n onInputValueChange={(v) => {\n setInput(v);\n if (isAsync) debouncedSetQuery(v);\n }}\n >\n <ComboboxChips aria-invalid={!!field.error || undefined} ref={anchor}>\n <DndContext\n sensors={sortSensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext items={chipIds} strategy={horizontalListSortingStrategy}>\n {items.map((raw, index) => {\n const parsed = parseChip(raw);\n return (\n <ChipPill\n key={chipIds[index]}\n id={chipIds[index]}\n parsed={parsed}\n onRemove={() => removeAt(index)}\n />\n );\n })}\n </SortableContext>\n </DndContext>\n <ComboboxChipsInput\n placeholder={items.length === 0 ? placeholder : undefined}\n aria-invalid={!!field.error || undefined}\n onKeyDown={(e) => {\n if (e.key === \"Backspace\" && input.length === 0) {\n if (items.length === 0) return;\n e.preventDefault();\n removeAt(items.length - 1);\n return;\n }\n if (e.key !== \"Enter\" || e.nativeEvent.isComposing) return;\n if (trimmedInput.length === 0) return;\n e.preventDefault();\n e.stopPropagation();\n if (atMax) return;\n // Prefer exact suggestion match, then exact field match, else\n // create a text entry.\n const exactSuggestion = suggestionItems.find(\n (o) => o.value.toLowerCase() === trimmedInput.toLowerCase(),\n );\n const exactField = fieldRefItems.find(\n (f) => f.label.toLowerCase() === trimmedInput.toLowerCase(),\n );\n const toAdd = exactSuggestion?.value ?? exactField?.value ?? trimmedInput;\n appendValue(toAdd);\n clearInput();\n }}\n />\n <ComboboxTrigger className=\"pz:ml-auto pz:bg-transparent pz:hover:bg-transparent\" />\n </ComboboxChips>\n <ComboboxContent anchor={anchor}>\n {loading && allItems.length > 0 && (\n <span\n aria-label=\"Loading\"\n className=\"pz:absolute pz:right-2 pz:top-2 pz:z-10 pz:inline-block pz:h-3 pz:w-3 pz:animate-spin pz:rounded-full pz:border-2 pz:border-muted-foreground/30 pz:border-t-muted-foreground\"\n />\n )}\n {loading && allItems.length === 0 && (\n <div\n role=\"status\"\n aria-label=\"Loading\"\n className=\"pz:flex pz:items-center pz:justify-center pz:py-6\"\n >\n <span className=\"pz:inline-block pz:h-5 pz:w-5 pz:animate-spin pz:rounded-full pz:border-2 pz:border-muted-foreground/30 pz:border-t-muted-foreground\" />\n </div>\n )}\n <ComboboxList\n className={cn(\n loading &&\n allItems.length > 0 &&\n \"pz:opacity-50 pz:transition-opacity pz:pointer-events-none\",\n )}\n >\n {atMax && (\n <div className=\"pz:px-2 pz:py-1.5 pz:text-xs pz:text-muted-foreground\">\n Maximum {maxItems} item{maxItems === 1 ? \"\" : \"s\"} reached.\n </div>\n )}\n\n {fieldRefItems.length > 0 && (\n <ComboboxGroup>\n <ComboboxLabel>Field references</ComboboxLabel>\n {fieldRefItems.map((it) => (\n <ComboboxItem key={`field:${it.value}`} value={it} disabled={atMax}>\n <span className=\"pz:inline-flex pz:items-center pz:rounded-sm pz:bg-sky-100 pz:px-1 pz:text-sky-900\">\n {it.label}\n </span>\n </ComboboxItem>\n ))}\n </ComboboxGroup>\n )}\n\n {suggestionItems.length > 0 && (\n <ComboboxGroup>\n <ComboboxLabel>Suggestions</ComboboxLabel>\n {suggestionItems.map((it) => (\n <ComboboxItem key={`text:${it.value}`} value={it} disabled={atMax}>\n {it.widgets && (\n <WidgetStrip widgets={pickLeadingWidgets(it.widgets)} size={14} />\n )}\n <span className=\"pz:flex-1 pz:truncate\">{it.label}</span>\n {it.widgets?.field_type && (\n <FieldTypeBadge\n type={it.widgets.field_type.type}\n format={it.widgets.field_type.format}\n />\n )}\n </ComboboxItem>\n ))}\n </ComboboxGroup>\n )}\n\n {showCreateRow && (\n <ComboboxItem\n key={`__create__:${trimmedInput}`}\n value={{ value: trimmedInput, label: `Add \"${trimmedInput}\"`, kind: \"create\" }}\n className=\"pz:italic\"\n disabled={atMax}\n >\n {`Add \"${trimmedInput}\"`}\n </ComboboxItem>\n )}\n\n {!loading && allItems.length === 0 && (\n <ComboboxEmpty>\n {trimmedInput.length > 0 ? \"No matches\" : \"Type to add a role\"}\n </ComboboxEmpty>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n </div>\n );\n}\n\nfunction pickLeadingWidgets(widgets: WidgetsByKind): WidgetsByKind {\n const { field_type: _ignored, ...rest } = widgets;\n return rest;\n}\n\nfunction ChipPill({\n id,\n parsed,\n onRemove,\n}: {\n id: string;\n parsed: ParsedChip;\n onRemove: () => void;\n}) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id,\n });\n const style: CSSProperties = {\n transform: transform\n ? `translate3d(${Math.round(transform.x)}px, ${Math.round(transform.y)}px, 0)`\n : undefined,\n transition,\n opacity: isDragging ? 0.6 : 1,\n };\n const isField = parsed.kind === \"field\";\n const label = isField ? parsed.fieldName : parsed.text;\n return (\n <ChipShell\n setNodeRef={setNodeRef}\n style={style}\n attributes={attributes}\n listeners={listeners}\n kind={parsed.kind}\n onRemove={onRemove}\n >\n <span className=\"pz:px-1\">\n {label || <span className=\"pz:text-muted-foreground\">empty</span>}\n </span>\n </ChipShell>\n );\n}\n\nfunction ChipShell({\n setNodeRef,\n style,\n attributes,\n listeners,\n kind,\n onRemove,\n children,\n}: {\n setNodeRef: (n: HTMLElement | null) => void;\n style: CSSProperties;\n // biome-ignore lint/suspicious/noExplicitAny: dnd-kit attribute/listener shape\n attributes: any;\n // biome-ignore lint/suspicious/noExplicitAny: dnd-kit attribute/listener shape\n listeners: any;\n kind: ParsedChip[\"kind\"];\n onRemove: () => void;\n children: ReactNode;\n}) {\n return (\n <span\n ref={setNodeRef}\n style={style}\n className={cn(kind === \"field\" ? FIELD_CHIP_CLASS : TEXT_CHIP_CLASS)}\n data-chip-kind={kind}\n {...attributes}\n >\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label=\"Drag to reorder\"\n className=\"pz:flex pz:items-center pz:justify-center pz:cursor-grab pz:text-muted-foreground pz:hover:text-foreground pz:touch-none\"\n {...listeners}\n >\n <IconGripVertical width={12} height={12} />\n </span>\n {children}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n className=\"pz:opacity-50 pz:hover:opacity-100\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n aria-label=\"Remove\"\n >\n <XIcon className=\"pz:pointer-events-none\" />\n </Button>\n </span>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tagged-ordered-multi-create-input.mjs","names":["XIcon"],"sources":["../../../../src/components/defaults/adapters/tagged-ordered-multi-create-input.tsx"],"sourcesContent":["import {\n closestCenter,\n DndContext,\n type DragEndEvent,\n KeyboardSensor,\n PointerSensor,\n useSensor,\n useSensors,\n} from \"@dnd-kit/core\";\nimport { horizontalListSortingStrategy, SortableContext, useSortable } from \"@dnd-kit/sortable\";\nimport type { PipesFieldDefinitionWithName, WidgetsByKind } from \"@pipe0/base\";\nimport {\n type CSSProperties,\n type ReactNode,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport useSWR from \"swr\";\nimport { useDebouncedFn } from \"../../../hooks/use-debounced-fn.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { FieldHandle } from \"../../../types/field-handle.js\";\nimport { FieldTypeBadge } from \"../../../widgets/field-type-badge.js\";\nimport { WidgetStrip } from \"../../../widgets/widget-strip.js\";\nimport { IconGripVertical, X as XIcon } from \"../../internal/icons.js\";\nimport { filterOptions, type PayloadOption } from \"../../internal/multi-select-popover-trigger.js\";\nimport { Button } from \"../../ui/button.js\";\nimport {\n Combobox,\n ComboboxChips,\n ComboboxChipsInput,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxGroup,\n ComboboxItem,\n ComboboxLabel,\n ComboboxList,\n ComboboxTrigger,\n useComboboxAnchor,\n} from \"../../ui/combobox.js\";\n\n/**\n * Detects whether a raw value is a single, exclusive Liquid field reference\n * (e.g. `\"{{ company_domain }}\"` or `\"{{ field | filter }}\"`). Mixed content\n * such as `\"Head of {{ company }}\"` returns false and is treated as text.\n */\nconst PURE_TAG_RE = /^\\s*\\{\\{\\s*([a-zA-Z_][a-zA-Z0-9_.]*)(?:\\s*\\|\\s*[^}]+)?\\s*\\}\\}\\s*$/;\n\ntype ParsedChip =\n | { kind: \"field\"; fieldName: string; raw: string }\n | { kind: \"text\"; text: string; raw: string };\n\nfunction parseChip(raw: string): ParsedChip {\n const m = raw.match(PURE_TAG_RE);\n if (m) return { kind: \"field\", fieldName: m[1], raw };\n return { kind: \"text\", text: raw, raw };\n}\n\n/**\n * Sky-blue pill matching `TagChipDecoration`'s field chip styling so a\n * field-reference chip outside tiptap reads identically to one inside\n * `LiquidEditor`.\n */\nconst FIELD_CHIP_CLASS =\n \"pz:flex pz:h-[calc(--spacing(5.25))] pz:w-fit pz:items-center pz:gap-1 pz:rounded-sm pz:bg-sky-100 pz:pl-0.5 pz:pr-0 pz:text-xs pz:font-medium pz:whitespace-nowrap pz:text-sky-900\";\n\nconst TEXT_CHIP_CLASS =\n \"pz:flex pz:h-[calc(--spacing(5.25))] pz:w-fit pz:items-center pz:gap-1 pz:rounded-sm pz:bg-muted pz:pl-0.5 pz:pr-0 pz:text-xs pz:font-medium pz:whitespace-nowrap pz:text-foreground\";\n\n/** Drop target for an item carried by the Combobox value channel. */\ntype Item = {\n /** Canonical value to store in the field — text as-is, or `{{ fieldName }}`. */\n value: string;\n /** Display label in the dropdown. */\n label: string;\n kind: \"field\" | \"text\" | \"create\";\n widgets?: WidgetsByKind;\n};\n\nexport function TaggedOrderedMultiCreateInputAdapter(\n field: FieldHandle<\"tagged_ordered_multi_create_input\">,\n) {\n const { items, setItems, options, loadOptions, inputFields, meta } = field;\n const maxItems = meta.maxItems;\n const placeholder = meta.placeholder;\n\n const [input, setInput] = useState(\"\");\n const [debouncedQuery, setDebouncedQuery] = useState(\"\");\n const [open, setOpen] = useState(false);\n\n const isAsync = typeof loadOptions === \"function\";\n const debouncedSetQuery = useDebouncedFn((q: string) => setDebouncedQuery(q), 300);\n\n // Async suggestions — same SWR pattern as `SuggestCombobox`.\n const instanceId = useId();\n const swrKey: readonly [string, string] | null =\n loadOptions && (open || debouncedQuery !== \"\") ? [instanceId, debouncedQuery] : null;\n const {\n data: asyncResults = null,\n isLoading: swrIsLoading,\n isValidating: swrIsValidating,\n } = useSWR<PayloadOption[], Error, readonly [string, string] | null>(\n swrKey,\n async ([, q]) => {\n if (!loadOptions) return [];\n const opts = await loadOptions(q);\n return opts.filter((o) => !!o.value);\n },\n { keepPreviousData: true },\n );\n const loading = swrIsLoading || swrIsValidating;\n\n const trimmedInput = input.trim();\n\n const fieldRefItems = useMemo<Item[]>(() => {\n if (!inputFields?.length) return [];\n const q = trimmedInput.toLowerCase();\n return inputFields\n .filter((f) => !q || f.resolvedName.toLowerCase().includes(q))\n .map<Item>((f) => ({\n value: `{{ ${f.resolvedName} }}`,\n label: f.resolvedName,\n kind: \"field\",\n }));\n }, [inputFields, trimmedInput]);\n\n const suggestionItems = useMemo<Item[]>(() => {\n const source: PayloadOption[] = isAsync ? (asyncResults ?? []) : (options ?? []);\n const ranked: PayloadOption[] = isAsync ? source : filterOptions(source, input);\n return ranked.map<Item>((o) => ({\n value: o.value,\n label: o.label,\n kind: \"text\",\n widgets: o.widgets,\n }));\n }, [isAsync, asyncResults, options, input]);\n\n const matchesExistingSuggestion = useMemo(\n () =>\n suggestionItems.some((o) => o.value.toLowerCase() === trimmedInput.toLowerCase()) ||\n fieldRefItems.some((f) => f.value.toLowerCase() === trimmedInput.toLowerCase()),\n [suggestionItems, fieldRefItems, trimmedInput],\n );\n\n const showCreateRow = trimmedInput.length > 0 && !matchesExistingSuggestion;\n\n // Flat items list — fed to base-ui as the universe of pickable items so its\n // keyboard navigation has a single ordering to work with. The visual\n // grouping is done by `ComboboxGroup` in the render below; base-ui ranges\n // over `items` linearly and matches by reference.\n const allItems = useMemo<Item[]>(() => {\n const list: Item[] = [];\n for (const f of fieldRefItems) list.push(f);\n for (const s of suggestionItems) list.push(s);\n if (showCreateRow) {\n list.push({\n value: trimmedInput,\n label: `Add \"${trimmedInput}\"`,\n kind: \"create\",\n });\n }\n return list;\n }, [fieldRefItems, suggestionItems, showCreateRow, trimmedInput]);\n\n const atMax = maxItems !== undefined && items.length >= maxItems;\n\n // Stable per-position ids so `@dnd-kit` keys survive even when two chips\n // share the same raw string (uncommon, but allowed — the user could insert\n // the same field reference twice in a waterfall).\n const idsRef = useRef<string[]>([]);\n if (idsRef.current.length !== items.length) {\n if (idsRef.current.length < items.length) {\n idsRef.current = [\n ...idsRef.current,\n ...Array.from({ length: items.length - idsRef.current.length }, () => crypto.randomUUID()),\n ];\n } else {\n idsRef.current = idsRef.current.slice(0, items.length);\n }\n }\n const chipIds = idsRef.current;\n\n const sortSensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor),\n );\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const from = chipIds.indexOf(String(active.id));\n const to = chipIds.indexOf(String(over.id));\n if (from === -1 || to === -1) return;\n const nextItems = [...items];\n const [movedValue] = nextItems.splice(from, 1);\n nextItems.splice(to, 0, movedValue);\n const nextIds = [...chipIds];\n const [movedId] = nextIds.splice(from, 1);\n nextIds.splice(to, 0, movedId);\n idsRef.current = nextIds;\n setItems(nextItems);\n };\n\n const removeAt = (index: number) => {\n idsRef.current = idsRef.current.filter((_, i) => i !== index);\n setItems(items.filter((_, i) => i !== index));\n };\n\n const appendValue = (value: string) => {\n if (atMax || !value) return;\n idsRef.current = [...idsRef.current, crypto.randomUUID()];\n setItems([...items, value]);\n };\n\n const clearInput = () => {\n setInput(\"\");\n if (isAsync) debouncedSetQuery(\"\");\n };\n\n const anchor = useComboboxAnchor();\n\n return (\n <div data-p0=\"input\" className=\"pz:flex pz:flex-col pz:gap-1\">\n <Combobox<Item, true>\n multiple\n items={allItems}\n filter={null}\n open={open}\n onOpenChange={setOpen}\n // Chips are rendered manually so base-ui doesn't manage them — keep\n // `value` empty and capture picks via `onValueChange`. Same trick as\n // `IncludeExcludeCombobox`.\n value={[] as Item[]}\n onValueChange={(next) => {\n if (!Array.isArray(next) || next.length === 0) return;\n const last = next[next.length - 1] as Item;\n appendValue(last.value);\n clearInput();\n }}\n isItemEqualToValue={(a, b) => (a as Item).value === (b as Item).value}\n itemToStringLabel={(item) => (item as Item).label}\n itemToStringValue={(item) => (item as Item).value}\n inputValue={input}\n onInputValueChange={(v) => {\n setInput(v);\n if (isAsync) debouncedSetQuery(v);\n }}\n >\n <ComboboxChips aria-invalid={!!field.error || undefined} ref={anchor}>\n <DndContext\n sensors={sortSensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext items={chipIds} strategy={horizontalListSortingStrategy}>\n {items.map((raw, index) => {\n const parsed = parseChip(raw);\n return (\n <ChipPill\n key={chipIds[index]}\n id={chipIds[index]}\n parsed={parsed}\n onRemove={() => removeAt(index)}\n />\n );\n })}\n </SortableContext>\n </DndContext>\n <ComboboxChipsInput\n placeholder={items.length === 0 ? placeholder : undefined}\n aria-invalid={!!field.error || undefined}\n onKeyDown={(e) => {\n if (e.key === \"Backspace\" && input.length === 0) {\n if (items.length === 0) return;\n e.preventDefault();\n removeAt(items.length - 1);\n return;\n }\n if (e.key !== \"Enter\" || e.nativeEvent.isComposing) return;\n if (trimmedInput.length === 0) return;\n e.preventDefault();\n e.stopPropagation();\n if (atMax) return;\n // Prefer exact suggestion match, then exact field match, else\n // create a text entry.\n const exactSuggestion = suggestionItems.find(\n (o) => o.value.toLowerCase() === trimmedInput.toLowerCase(),\n );\n const exactField = fieldRefItems.find(\n (f) => f.label.toLowerCase() === trimmedInput.toLowerCase(),\n );\n const toAdd = exactSuggestion?.value ?? exactField?.value ?? trimmedInput;\n appendValue(toAdd);\n clearInput();\n }}\n />\n <ComboboxTrigger className=\"pz:ml-auto pz:bg-transparent pz:hover:bg-transparent\" />\n </ComboboxChips>\n <ComboboxContent anchor={anchor}>\n {loading && allItems.length > 0 && (\n <span\n aria-label=\"Loading\"\n className=\"pz:absolute pz:right-2 pz:top-2 pz:z-10 pz:inline-block pz:h-3 pz:w-3 pz:animate-spin pz:rounded-full pz:border-2 pz:border-muted-foreground/30 pz:border-t-muted-foreground\"\n />\n )}\n {loading && allItems.length === 0 && (\n <div\n role=\"status\"\n aria-label=\"Loading\"\n className=\"pz:flex pz:items-center pz:justify-center pz:py-6\"\n >\n <span className=\"pz:inline-block pz:h-5 pz:w-5 pz:animate-spin pz:rounded-full pz:border-2 pz:border-muted-foreground/30 pz:border-t-muted-foreground\" />\n </div>\n )}\n <ComboboxList\n className={cn(\n loading &&\n allItems.length > 0 &&\n \"pz:opacity-50 pz:transition-opacity pz:pointer-events-none\",\n )}\n >\n {atMax && (\n <div className=\"pz:px-2 pz:py-1.5 pz:text-xs pz:text-muted-foreground\">\n Maximum {maxItems} item{maxItems === 1 ? \"\" : \"s\"} reached.\n </div>\n )}\n\n {fieldRefItems.length > 0 && (\n <ComboboxGroup>\n <ComboboxLabel>Field references</ComboboxLabel>\n {fieldRefItems.map((it) => (\n <ComboboxItem key={`field:${it.value}`} value={it} disabled={atMax}>\n <span className=\"pz:inline-flex pz:items-center pz:rounded-sm pz:bg-sky-100 pz:px-1 pz:text-sky-900\">\n {it.label}\n </span>\n </ComboboxItem>\n ))}\n </ComboboxGroup>\n )}\n\n {suggestionItems.length > 0 && (\n <ComboboxGroup>\n <ComboboxLabel>Suggestions</ComboboxLabel>\n {suggestionItems.map((it) => (\n <ComboboxItem key={`text:${it.value}`} value={it} disabled={atMax}>\n {it.widgets && (\n <WidgetStrip widgets={pickLeadingWidgets(it.widgets)} size={14} />\n )}\n <span className=\"pz:flex-1 pz:truncate\">{it.label}</span>\n {it.widgets?.field_type && (\n <FieldTypeBadge\n type={it.widgets.field_type.type}\n format={it.widgets.field_type.format}\n />\n )}\n </ComboboxItem>\n ))}\n </ComboboxGroup>\n )}\n\n {showCreateRow && (\n <ComboboxItem\n key={`__create__:${trimmedInput}`}\n value={{ value: trimmedInput, label: `Add \"${trimmedInput}\"`, kind: \"create\" }}\n className=\"pz:italic\"\n disabled={atMax}\n >\n {`Add \"${trimmedInput}\"`}\n </ComboboxItem>\n )}\n\n {!loading && allItems.length === 0 && (\n <ComboboxEmpty>\n {trimmedInput.length > 0 ? \"No matches\" : \"Type to add a role\"}\n </ComboboxEmpty>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n </div>\n );\n}\n\nfunction pickLeadingWidgets(widgets: WidgetsByKind): WidgetsByKind {\n const { field_type: _ignored, ...rest } = widgets;\n return rest;\n}\n\nfunction ChipPill({\n id,\n parsed,\n onRemove,\n}: {\n id: string;\n parsed: ParsedChip;\n onRemove: () => void;\n}) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id,\n });\n const style: CSSProperties = {\n transform: transform\n ? `translate3d(${Math.round(transform.x)}px, ${Math.round(transform.y)}px, 0)`\n : undefined,\n transition,\n opacity: isDragging ? 0.6 : 1,\n };\n const isField = parsed.kind === \"field\";\n const label = isField ? parsed.fieldName : parsed.text;\n return (\n <ChipShell\n setNodeRef={setNodeRef}\n style={style}\n attributes={attributes}\n listeners={listeners}\n kind={parsed.kind}\n onRemove={onRemove}\n >\n <span className=\"pz:px-1\">\n {label || <span className=\"pz:text-muted-foreground\">empty</span>}\n </span>\n </ChipShell>\n );\n}\n\nfunction ChipShell({\n setNodeRef,\n style,\n attributes,\n listeners,\n kind,\n onRemove,\n children,\n}: {\n setNodeRef: (n: HTMLElement | null) => void;\n style: CSSProperties;\n // biome-ignore lint/suspicious/noExplicitAny: dnd-kit attribute/listener shape\n attributes: any;\n // biome-ignore lint/suspicious/noExplicitAny: dnd-kit attribute/listener shape\n listeners: any;\n kind: ParsedChip[\"kind\"];\n onRemove: () => void;\n children: ReactNode;\n}) {\n return (\n <span\n ref={setNodeRef}\n style={style}\n className={cn(kind === \"field\" ? FIELD_CHIP_CLASS : TEXT_CHIP_CLASS)}\n data-chip-kind={kind}\n {...attributes}\n >\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label=\"Drag to reorder\"\n className=\"pz:flex pz:items-center pz:justify-center pz:cursor-grab pz:text-muted-foreground pz:hover:text-foreground pz:touch-none\"\n {...listeners}\n >\n <IconGripVertical width={12} height={12} />\n </span>\n {children}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n className=\"pz:opacity-50 pz:hover:opacity-100\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n aria-label=\"Remove\"\n >\n <XIcon className=\"pz:pointer-events-none\" />\n </Button>\n </span>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.mjs";
|
|
2
|
+
import { ChevronDown } from "../../internal/icons.mjs";
|
|
2
3
|
import { WidgetStrip } from "../../../widgets/widget-strip.mjs";
|
|
3
4
|
import { LiquidEditor } from "../../internal/LiquidEditor/LiquidEditor.mjs";
|
|
4
5
|
import { useMemo, useState } from "react";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { ChevronDown } from "lucide-react";
|
|
7
7
|
import { autoUpdate, flip, offset, shift, size, useDismiss, useFloating, useInteractions, useTransitionStyles } from "@floating-ui/react";
|
|
8
8
|
|
|
9
9
|
//#region src/components/defaults/adapters/tagged-text-input.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tagged-text-input.mjs","names":[],"sources":["../../../../src/components/defaults/adapters/tagged-text-input.tsx"],"sourcesContent":["import {\n autoUpdate,\n flip,\n offset,\n shift,\n size,\n useDismiss,\n useFloating,\n useInteractions,\n useTransitionStyles,\n} from \"@floating-ui/react\";\nimport type { StoreOption, TaggedTextMeta } from \"@pipe0/base\";\nimport { ChevronDown } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { FieldHandle } from \"../../../types/field-handle.js\";\nimport { WidgetStrip } from \"../../../widgets/widget-strip.js\";\nimport { LiquidEditor } from \"../../internal/LiquidEditor/LiquidEditor.js\";\n\n/**\n * Adapter for `pipesTaggedTextInput`. Renders a tiptap-backed editor that\n * supports `/` to open a unified reference picker (input fields filtered\n * by `expectedTagType`, secrets, constants).\n *\n * When `meta.optionsDef` is present, the input also gains a value-suggestions\n * popover (e.g. listing the user's existing pipe0 sheets). The popover opens\n * on focus, hides automatically when the user is composing a `{{ tag }}`,\n * and is gated by `meta.optionsDef.enabledIf` (sub-feature gate — the input\n * itself stays editable in every state). Picking an option REPLACES the\n * input value with the option's value.\n */\nexport function TaggedTextInputAdapter(field: FieldHandle<\"tagged_text_input\">) {\n const meta = field.meta as TaggedTextMeta;\n const hasError = !!field.error;\n const hasSuggestions = !!meta.optionsDef;\n\n const [open, setOpen] = useState(false);\n\n // Hide the popover whenever the user is composing a `{{ tag }}` — the\n // LiquidEditor SuggestionMenu owns that interaction.\n const valueIsTag = useMemo(() => /\\{\\{[^}]*\\}?\\}?/.test(field.value ?? \"\"), [field.value]);\n\n const hasQuery = (field.value ?? \"\").trim().length > 0;\n\n const popoverShow = hasSuggestions && open && !valueIsTag;\n\n // Floating UI reads positioning straight from the wrapper element via\n // `refs.setReference`; `whileElementsMounted: autoUpdate` subscribes to the\n // ancestor scroll/resize that actually matters (no global window listener,\n // no React state for the rect).\n const { refs, floatingStyles, context } = useFloating({\n open: popoverShow,\n onOpenChange: (next) => {\n if (!next) setOpen(false);\n },\n placement: \"bottom-start\",\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(4),\n flip({ mainAxis: true, crossAxis: false }),\n shift(),\n size({\n apply({ elements, rects }) {\n elements.floating.style.minWidth = `${rects.reference.width}px`;\n elements.floating.style.maxWidth = \"32rem\";\n },\n }),\n ],\n });\n\n const { isMounted, styles: transitionStyles } = useTransitionStyles(context);\n const dismiss = useDismiss(context, {\n outsidePress: true,\n outsidePressEvent: \"mousedown\",\n });\n const { getFloatingProps } = useInteractions([dismiss]);\n\n // Filter options client-side by current input text. Empty input → all options.\n const visibleOptions = useMemo(() => {\n const q = (field.value ?? \"\").trim().toLowerCase();\n if (!q) return field.options;\n return field.options.filter(\n (o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q),\n );\n }, [field.options, field.value]);\n\n return (\n <div data-p0=\"input\" className=\"pz:flex pz:flex-col pz:gap-1\">\n <div\n ref={refs.setReference}\n className={cn(\n \"pz:flex pz:rounded-lg pz:border pz:border-input pz:bg-transparent\",\n meta.multiline ? \"pz:items-start\" : \"pz:items-center\",\n hasError && \"pz:border-destructive/60\",\n )}\n aria-invalid={hasError || undefined}\n onFocusCapture={() => {\n if (hasSuggestions) setOpen(true);\n }}\n onBlurCapture={(e) => {\n // Only close when focus leaves both the wrapper AND the popover.\n const next = e.relatedTarget as Node | null;\n if (next && e.currentTarget.contains(next)) return;\n if (next && document.querySelector(\"[data-p0='tagged-suggestions']\")?.contains(next)) {\n return;\n }\n setOpen(false);\n }}\n >\n <div className=\"pz:flex-1 pz:min-w-0\">\n <LiquidEditor\n value={field.value ?? \"\"}\n onChange={(v) => field.setValue(v)}\n inputFields={meta.inputFields ?? []}\n searchSecrets={field.searchSecrets}\n searchConstants={field.searchConstants}\n placeholder={meta.placeholder}\n multiline={meta.multiline}\n expectedFieldType={meta.expectedTagType}\n directives={[]}\n />\n </div>\n {hasSuggestions && (\n <div\n className={cn(\n \"pz:flex pz:items-center pz:gap-1 pz:pr-2 pz:text-muted-foreground\",\n meta.multiline && \"pz:pt-1\",\n )}\n >\n <ChevronDown className=\"pz:size-4 pz:pointer-events-none\" aria-hidden=\"true\" />\n </div>\n )}\n </div>\n\n {hasSuggestions && isMounted && (\n <div\n ref={refs.setFloating}\n data-p0=\"tagged-suggestions\"\n style={{ ...floatingStyles, ...transitionStyles, zIndex: 50 }}\n {...getFloatingProps({\n // Prevent stealing focus from the editor when the user clicks an option.\n onMouseDown: (e) => e.preventDefault(),\n })}\n className=\"pz:relative pz:rounded-lg pz:border pz:border-input pz:bg-popover pz:text-popover-foreground pz:shadow-md pz:overflow-hidden\"\n >\n {field.pending && visibleOptions.length > 0 && (\n // biome-ignore lint/a11y/useAriaPropsSupportedByRole: not relevant\n <span\n aria-label=\"Loading\"\n className=\"pz:absolute pz:right-2 pz:top-2 pz:z-10 pz:inline-block pz:h-3 pz:w-3 pz:animate-spin pz:rounded-full pz:border-2 pz:border-muted-foreground/30 pz:border-t-muted-foreground\"\n />\n )}\n <SuggestionsBody\n suggestionsDisabled={field.suggestionsDisabled}\n reason={field.suggestionsDisabledReason}\n pending={field.pending}\n options={visibleOptions}\n hasQuery={hasQuery}\n onPick={(value) => {\n field.setValue(value);\n setOpen(false);\n }}\n />\n </div>\n )}\n </div>\n );\n}\n\nfunction SuggestionsBody({\n suggestionsDisabled,\n reason,\n pending,\n options,\n hasQuery,\n onPick,\n}: {\n suggestionsDisabled: boolean;\n reason?: string;\n pending: boolean;\n options: StoreOption[];\n hasQuery: boolean;\n onPick: (value: string) => void;\n}) {\n if (suggestionsDisabled) {\n return (\n <div className=\"pz:px-3 pz:py-2 pz:text-xs pz:text-muted-foreground\">\n {reason ?? \"Suggestions unavailable.\"}\n </div>\n );\n }\n if (pending && options.length === 0) {\n // Initial load — render a visible spinner so the popover has feedback.\n return (\n <div\n role=\"status\"\n aria-label=\"Loading\"\n className=\"pz:flex pz:items-center pz:justify-center pz:py-6\"\n >\n <span className=\"pz:inline-block pz:h-5 pz:w-5 pz:animate-spin pz:rounded-full pz:border-2 pz:border-muted-foreground/30 pz:border-t-muted-foreground\" />\n </div>\n );\n }\n if (options.length === 0) {\n return (\n <div className=\"pz:px-3 pz:py-2 pz:text-xs pz:text-muted-foreground\">\n {hasQuery ? \"No matches\" : \"No options available\"}\n </div>\n );\n }\n return (\n <ul\n className=\"pz:flex pz:flex-col pz:gap-0.5 pz:p-1 pz:transition-opacity\"\n style={pending ? { opacity: 0.5, pointerEvents: \"none\" } : undefined}\n >\n {options.map((opt) => (\n <li key={opt.value}>\n <button\n type=\"button\"\n role=\"option\"\n className=\"pz:flex pz:w-full pz:items-center pz:gap-2 pz:rounded-sm pz:px-2 pz:py-1.5 pz:text-sm pz:text-left pz:cursor-pointer pz:hover:bg-accent pz:hover:text-accent-foreground\"\n onClick={() => onPick(opt.value)}\n >\n <WidgetStrip widgets={opt.widgets} />\n <span>{opt.label}</span>\n </button>\n </li>\n ))}\n </ul>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tagged-text-input.mjs","names":[],"sources":["../../../../src/components/defaults/adapters/tagged-text-input.tsx"],"sourcesContent":["import {\n autoUpdate,\n flip,\n offset,\n shift,\n size,\n useDismiss,\n useFloating,\n useInteractions,\n useTransitionStyles,\n} from \"@floating-ui/react\";\nimport type { StoreOption, TaggedTextMeta } from \"@pipe0/base\";\nimport { useMemo, useState } from \"react\";\nimport { cn } from \"../../../lib/utils.js\";\nimport type { FieldHandle } from \"../../../types/field-handle.js\";\nimport { WidgetStrip } from \"../../../widgets/widget-strip.js\";\nimport { ChevronDown } from \"../../internal/icons.js\";\nimport { LiquidEditor } from \"../../internal/LiquidEditor/LiquidEditor.js\";\n\n/**\n * Adapter for `pipesTaggedTextInput`. Renders a tiptap-backed editor that\n * supports `/` to open a unified reference picker (input fields filtered\n * by `expectedTagType`, secrets, constants).\n *\n * When `meta.optionsDef` is present, the input also gains a value-suggestions\n * popover (e.g. listing the user's existing pipe0 sheets). The popover opens\n * on focus, hides automatically when the user is composing a `{{ tag }}`,\n * and is gated by `meta.optionsDef.enabledIf` (sub-feature gate — the input\n * itself stays editable in every state). Picking an option REPLACES the\n * input value with the option's value.\n */\nexport function TaggedTextInputAdapter(field: FieldHandle<\"tagged_text_input\">) {\n const meta = field.meta as TaggedTextMeta;\n const hasError = !!field.error;\n const hasSuggestions = !!meta.optionsDef;\n\n const [open, setOpen] = useState(false);\n\n // Hide the popover whenever the user is composing a `{{ tag }}` — the\n // LiquidEditor SuggestionMenu owns that interaction.\n const valueIsTag = useMemo(() => /\\{\\{[^}]*\\}?\\}?/.test(field.value ?? \"\"), [field.value]);\n\n const hasQuery = (field.value ?? \"\").trim().length > 0;\n\n const popoverShow = hasSuggestions && open && !valueIsTag;\n\n // Floating UI reads positioning straight from the wrapper element via\n // `refs.setReference`; `whileElementsMounted: autoUpdate` subscribes to the\n // ancestor scroll/resize that actually matters (no global window listener,\n // no React state for the rect).\n const { refs, floatingStyles, context } = useFloating({\n open: popoverShow,\n onOpenChange: (next) => {\n if (!next) setOpen(false);\n },\n placement: \"bottom-start\",\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(4),\n flip({ mainAxis: true, crossAxis: false }),\n shift(),\n size({\n apply({ elements, rects }) {\n elements.floating.style.minWidth = `${rects.reference.width}px`;\n elements.floating.style.maxWidth = \"32rem\";\n },\n }),\n ],\n });\n\n const { isMounted, styles: transitionStyles } = useTransitionStyles(context);\n const dismiss = useDismiss(context, {\n outsidePress: true,\n outsidePressEvent: \"mousedown\",\n });\n const { getFloatingProps } = useInteractions([dismiss]);\n\n // Filter options client-side by current input text. Empty input → all options.\n const visibleOptions = useMemo(() => {\n const q = (field.value ?? \"\").trim().toLowerCase();\n if (!q) return field.options;\n return field.options.filter(\n (o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q),\n );\n }, [field.options, field.value]);\n\n return (\n <div data-p0=\"input\" className=\"pz:flex pz:flex-col pz:gap-1\">\n <div\n ref={refs.setReference}\n className={cn(\n \"pz:flex pz:rounded-lg pz:border pz:border-input pz:bg-transparent\",\n meta.multiline ? \"pz:items-start\" : \"pz:items-center\",\n hasError && \"pz:border-destructive/60\",\n )}\n aria-invalid={hasError || undefined}\n onFocusCapture={() => {\n if (hasSuggestions) setOpen(true);\n }}\n onBlurCapture={(e) => {\n // Only close when focus leaves both the wrapper AND the popover.\n const next = e.relatedTarget as Node | null;\n if (next && e.currentTarget.contains(next)) return;\n if (next && document.querySelector(\"[data-p0='tagged-suggestions']\")?.contains(next)) {\n return;\n }\n setOpen(false);\n }}\n >\n <div className=\"pz:flex-1 pz:min-w-0\">\n <LiquidEditor\n value={field.value ?? \"\"}\n onChange={(v) => field.setValue(v)}\n inputFields={meta.inputFields ?? []}\n searchSecrets={field.searchSecrets}\n searchConstants={field.searchConstants}\n placeholder={meta.placeholder}\n multiline={meta.multiline}\n expectedFieldType={meta.expectedTagType}\n directives={[]}\n />\n </div>\n {hasSuggestions && (\n <div\n className={cn(\n \"pz:flex pz:items-center pz:gap-1 pz:pr-2 pz:text-muted-foreground\",\n meta.multiline && \"pz:pt-1\",\n )}\n >\n <ChevronDown className=\"pz:size-4 pz:pointer-events-none\" aria-hidden=\"true\" />\n </div>\n )}\n </div>\n\n {hasSuggestions && isMounted && (\n <div\n ref={refs.setFloating}\n data-p0=\"tagged-suggestions\"\n style={{ ...floatingStyles, ...transitionStyles, zIndex: 50 }}\n {...getFloatingProps({\n // Prevent stealing focus from the editor when the user clicks an option.\n onMouseDown: (e) => e.preventDefault(),\n })}\n className=\"pz:relative pz:rounded-lg pz:border pz:border-input pz:bg-popover pz:text-popover-foreground pz:shadow-md pz:overflow-hidden\"\n >\n {field.pending && visibleOptions.length > 0 && (\n // biome-ignore lint/a11y/useAriaPropsSupportedByRole: not relevant\n <span\n aria-label=\"Loading\"\n className=\"pz:absolute pz:right-2 pz:top-2 pz:z-10 pz:inline-block pz:h-3 pz:w-3 pz:animate-spin pz:rounded-full pz:border-2 pz:border-muted-foreground/30 pz:border-t-muted-foreground\"\n />\n )}\n <SuggestionsBody\n suggestionsDisabled={field.suggestionsDisabled}\n reason={field.suggestionsDisabledReason}\n pending={field.pending}\n options={visibleOptions}\n hasQuery={hasQuery}\n onPick={(value) => {\n field.setValue(value);\n setOpen(false);\n }}\n />\n </div>\n )}\n </div>\n );\n}\n\nfunction SuggestionsBody({\n suggestionsDisabled,\n reason,\n pending,\n options,\n hasQuery,\n onPick,\n}: {\n suggestionsDisabled: boolean;\n reason?: string;\n pending: boolean;\n options: StoreOption[];\n hasQuery: boolean;\n onPick: (value: string) => void;\n}) {\n if (suggestionsDisabled) {\n return (\n <div className=\"pz:px-3 pz:py-2 pz:text-xs pz:text-muted-foreground\">\n {reason ?? \"Suggestions unavailable.\"}\n </div>\n );\n }\n if (pending && options.length === 0) {\n // Initial load — render a visible spinner so the popover has feedback.\n return (\n <div\n role=\"status\"\n aria-label=\"Loading\"\n className=\"pz:flex pz:items-center pz:justify-center pz:py-6\"\n >\n <span className=\"pz:inline-block pz:h-5 pz:w-5 pz:animate-spin pz:rounded-full pz:border-2 pz:border-muted-foreground/30 pz:border-t-muted-foreground\" />\n </div>\n );\n }\n if (options.length === 0) {\n return (\n <div className=\"pz:px-3 pz:py-2 pz:text-xs pz:text-muted-foreground\">\n {hasQuery ? \"No matches\" : \"No options available\"}\n </div>\n );\n }\n return (\n <ul\n className=\"pz:flex pz:flex-col pz:gap-0.5 pz:p-1 pz:transition-opacity\"\n style={pending ? { opacity: 0.5, pointerEvents: \"none\" } : undefined}\n >\n {options.map((opt) => (\n <li key={opt.value}>\n <button\n type=\"button\"\n role=\"option\"\n className=\"pz:flex pz:w-full pz:items-center pz:gap-2 pz:rounded-sm pz:px-2 pz:py-1.5 pz:text-sm pz:text-left pz:cursor-pointer pz:hover:bg-accent pz:hover:text-accent-foreground\"\n onClick={() => onPick(opt.value)}\n >\n <WidgetStrip widgets={opt.widgets} />\n <span>{opt.label}</span>\n </button>\n </li>\n ))}\n </ul>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.mjs";
|
|
2
2
|
import { useCatalogConfig } from "../../../context/catalog-config-context.mjs";
|
|
3
|
+
import { X } from "../../internal/icons.mjs";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { X } from "lucide-react";
|
|
5
5
|
|
|
6
6
|
//#region src/components/defaults/catalog/active-filter-pill.tsx
|
|
7
7
|
function DefaultActiveFilterPill({ label, value, onRemove, className }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"active-filter-pill.mjs","names":[],"sources":["../../../../src/components/defaults/catalog/active-filter-pill.tsx"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"active-filter-pill.mjs","names":[],"sources":["../../../../src/components/defaults/catalog/active-filter-pill.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { useCatalogConfig } from \"../../../context/catalog-config-context.js\";\nimport { cn } from \"../../../lib/utils.js\";\nimport { X } from \"../../internal/icons.js\";\n\nexport interface DefaultActiveFilterPillProps {\n label?: ReactNode;\n value: ReactNode;\n onRemove: () => void;\n className?: string;\n}\n\nexport function DefaultActiveFilterPill({\n label,\n value,\n onRemove,\n className,\n}: DefaultActiveFilterPillProps) {\n const { classNames } = useCatalogConfig();\n return (\n <span\n data-p0=\"catalog-active-filter\"\n className={cn(\n \"pz:inline-flex pz:items-center pz:gap-1 pz:px-2 pz:py-0.5 pz:text-xs pz:rounded-md pz:bg-primary/10 pz:text-primary\",\n classNames?.activeFilterPill,\n className,\n )}\n >\n {label ? <span className=\"pz:text-primary/60\">{label}:</span> : null}\n {value}\n <button\n type=\"button\"\n onClick={onRemove}\n className=\"pz:ml-0.5 pz:hover:text-primary/80 pz:cursor-pointer\"\n >\n <X className=\"pz:size-3\" />\n </button>\n </span>\n );\n}\n"],"mappings":";;;;;;AAYA,SAAgB,wBAAwB,EACtC,OACA,OACA,UACA,aAC+B;CAC/B,MAAM,EAAE,eAAe,kBAAkB;AACzC,QACE,qBAAC,QAAD;EACE,WAAQ;EACR,WAAW,GACT,uHACA,YAAY,kBACZ,UACD;YANH;GAQG,QAAQ,qBAAC,QAAD;IAAM,WAAU;cAAhB,CAAsC,OAAM,IAAQ;QAAG;GAC/D;GACD,oBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,WAAU;cAEV,oBAAC,GAAD,EAAG,WAAU,aAAc;IACpB;GACJ"}
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,UAuCT,sBAAA;EACf,MAAA;EACA,MAAA;EACA,OAAA,GAAU,IAAA;EA1DqB;EA4D/B,QAAA,GAAW,SAAA;EACX,gBAAA,GAAmB,SAAA;AAAA;AAAA,UAGJ,sBAAA;EACf,SAAA;EAvDA;EAyDA,MAAA;EAvDA;EAyDA,aAAA,IAAiB,SAAA;EAvDjB;EAyDA,gBAAA,IAAoB,SAAA;EACpB,KAAA,GAAQ,SAAA;EACR,SAAA,IAAa,SAAA,aAAsB,SAAA;EACnC,SAAA;EACA,OAAA,GAAU,qBAAA;EAlEV;EAoEA,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;EA1Mf;EA4MA,OAAA;EA3MA;EA6MA,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"}
|
|
@@ -2,10 +2,10 @@ import { cn } from "../../../lib/utils.mjs";
|
|
|
2
2
|
import { useCatalogConfig } from "../../../context/catalog-config-context.mjs";
|
|
3
3
|
import { useAnyCatalogCardOptional, useAnyCatalogContextOptional } from "../../../context/catalog-card-context.mjs";
|
|
4
4
|
import { CatalogCardBadge } from "./card-primitives.mjs";
|
|
5
|
+
import { Check, Coin, Copy, ExternalLink, ListChecks, ListPlus } from "../../internal/icons.mjs";
|
|
5
6
|
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover.mjs";
|
|
6
7
|
import { useState } from "react";
|
|
7
8
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { Check, Coins, Copy, ExternalLink, ListChecks, ListPlus } from "lucide-react";
|
|
9
9
|
|
|
10
10
|
//#region src/components/defaults/catalog/card-derived.tsx
|
|
11
11
|
/**
|
|
@@ -25,16 +25,19 @@ function CatalogCreditBadge({ creditAmount, costMode, freeLabel = "Free", credit
|
|
|
25
25
|
className: cn("pz:whitespace-nowrap", className),
|
|
26
26
|
children: mode === "usage" ? /* @__PURE__ */ jsxs("span", {
|
|
27
27
|
className: "pz:inline-flex pz:items-center pz:gap-1",
|
|
28
|
-
children: [/* @__PURE__ */ jsx(
|
|
28
|
+
children: [/* @__PURE__ */ jsx(Coin, { className: "pz:size-3.5 pz:opacity-70" }), /* @__PURE__ */ jsx("span", { children: "Usage" })]
|
|
29
29
|
}) : display > 0 ? /* @__PURE__ */ jsxs("span", {
|
|
30
|
-
className: "pz:inline-flex pz:items-center pz:gap-
|
|
30
|
+
className: "pz:inline-flex pz:items-center pz:gap-1 pz:tabular-nums",
|
|
31
31
|
children: [
|
|
32
32
|
creditPrefix ? /* @__PURE__ */ jsx("span", {
|
|
33
|
-
className: "pz:mr-
|
|
33
|
+
className: "pz:mr-0.5",
|
|
34
34
|
children: creditPrefix
|
|
35
35
|
}) : null,
|
|
36
|
-
/* @__PURE__ */ jsx(
|
|
37
|
-
/* @__PURE__ */ jsx(
|
|
36
|
+
/* @__PURE__ */ jsx(Coin, { className: "pz:size-3.5 pz:opacity-70" }),
|
|
37
|
+
/* @__PURE__ */ jsx("span", {
|
|
38
|
+
className: "pz:font-medium pz:text-foreground/75",
|
|
39
|
+
children: display
|
|
40
|
+
}),
|
|
38
41
|
/* @__PURE__ */ jsxs("span", { children: ["/ ", denom] })
|
|
39
42
|
]
|
|
40
43
|
}) : freeLabel
|
|
@@ -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 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`. */\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 (\"searchId\" in card) return card.searchId;\n if (\"effectId\" in card) return card.effectId;\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 { 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 { Check, Coin, Copy, ExternalLink, ListChecks, ListPlus } from \"../../internal/icons.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 <Coin className=\"pz:size-3.5 pz:opacity-70\" />\n <span>Usage</span>\n </span>\n ) : display > 0 ? (\n // Coin, amount, unit: the amount is what you compare, so it carries\n // the weight; the coin and the unit stay quiet around it.\n <span className=\"pz:inline-flex pz:items-center pz:gap-1 pz:tabular-nums\">\n {creditPrefix ? <span className=\"pz:mr-0.5\">{creditPrefix}</span> : null}\n <Coin className=\"pz:size-3.5 pz:opacity-70\" />\n <span className=\"pz:font-medium pz:text-foreground/75\">{display}</span>\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`. */\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 (\"searchId\" in card) return card.searchId;\n if (\"effectId\" in card) return card.effectId;\n return undefined;\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,8 @@ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
|
5
5
|
interface CatalogCategorySectionProps {
|
|
6
6
|
/** Display label (e.g. "People"). */
|
|
7
7
|
label: ReactNode;
|
|
8
|
-
/**
|
|
8
|
+
/** @deprecated No longer rendered — sections carry no colour dot. Kept so
|
|
9
|
+
* existing callers keep compiling. */
|
|
9
10
|
color?: string;
|
|
10
11
|
/** Count rendered next to the label. */
|
|
11
12
|
count?: number;
|
|
@@ -25,7 +26,6 @@ interface CatalogCategorySectionProps {
|
|
|
25
26
|
*/
|
|
26
27
|
declare function CatalogCategorySection({
|
|
27
28
|
label,
|
|
28
|
-
color,
|
|
29
29
|
count,
|
|
30
30
|
defaultOpen,
|
|
31
31
|
open,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"category-section.d.mts","names":[],"sources":["../../../../src/components/defaults/catalog/category-section.tsx"],"mappings":";;;;UAIiB,2BAAA;;EAEf,KAAA,EAAO,SAAA;EAFQ
|
|
1
|
+
{"version":3,"file":"category-section.d.mts","names":[],"sources":["../../../../src/components/defaults/catalog/category-section.tsx"],"mappings":";;;;UAIiB,2BAAA;;EAEf,KAAA,EAAO,SAAA;EAFQ;;EAKf,KAAA;EAWoB;EATpB,KAAA;EALO;EAOP,WAAA;EAFA;EAIA,IAAA;EACA,YAAA,IAAgB,IAAA;EAChB,SAAA;EADgB;EAGhB,aAAA;EACA,QAAA,GAAW,SAAA;AAAA;;;;AAOb;iBAAgB,sBAAA,CAAA;EACd,KAAA;EACA,KAAA;EACA,WAAA;EACA,IAAA;EACA,YAAA;EACA,SAAA;EACA,aAAA;EACA;AAAA,GACC,2BAAA,GAA2B,oBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { cn } from "../../../lib/utils.mjs";
|
|
2
|
+
import { ChevronDown } from "../../internal/icons.mjs";
|
|
2
3
|
import { useState } from "react";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { ChevronDown } from "lucide-react";
|
|
5
5
|
|
|
6
6
|
//#region src/components/defaults/catalog/category-section.tsx
|
|
7
7
|
/**
|
|
8
8
|
* Collapsible category section header. Used to group catalog cards by
|
|
9
9
|
* category. Click the header (or chevron) to toggle the body.
|
|
10
10
|
*/
|
|
11
|
-
function CatalogCategorySection({ label,
|
|
11
|
+
function CatalogCategorySection({ label, count, defaultOpen = true, open, onOpenChange, className, bodyClassName, children }) {
|
|
12
12
|
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
13
13
|
const isControlled = open !== void 0;
|
|
14
14
|
const isOpen = isControlled ? !!open : internalOpen;
|
|
@@ -26,25 +26,20 @@ function CatalogCategorySection({ label, color, count, defaultOpen = true, open,
|
|
|
26
26
|
"data-p0": "catalog-category-section-header",
|
|
27
27
|
onClick: toggle,
|
|
28
28
|
"aria-expanded": isOpen,
|
|
29
|
-
className: "pz:flex pz:items-center pz:gap-2 pz:
|
|
29
|
+
className: "pz:group/cat pz:flex pz:items-center pz:gap-2 pz:self-start pz:rounded-md pz:py-1 pz:text-left pz:bg-transparent pz:border-0 pz:cursor-pointer",
|
|
30
30
|
children: [
|
|
31
|
-
/* @__PURE__ */ jsx(ChevronDown, {
|
|
32
|
-
"aria-hidden": "true",
|
|
33
|
-
className: cn("pz:size-3.5 pz:text-muted-foreground pz:shrink-0 pz:transition-transform", isOpen ? void 0 : "pz:-rotate-90")
|
|
34
|
-
}),
|
|
35
|
-
color ? /* @__PURE__ */ jsx("span", {
|
|
36
|
-
"aria-hidden": "true",
|
|
37
|
-
className: "pz:size-1.5 pz:rounded-full pz:shrink-0",
|
|
38
|
-
style: { backgroundColor: color }
|
|
39
|
-
}) : null,
|
|
40
31
|
/* @__PURE__ */ jsx("span", {
|
|
41
|
-
className: "pz:text-
|
|
32
|
+
className: "pz:text-sm pz:font-semibold pz:tracking-[-0.006em] pz:text-foreground",
|
|
42
33
|
children: label
|
|
43
34
|
}),
|
|
44
35
|
typeof count === "number" ? /* @__PURE__ */ jsx("span", {
|
|
45
|
-
className: "pz:text-[
|
|
36
|
+
className: "pz:inline-flex pz:h-4 pz:min-w-4 pz:items-center pz:justify-center pz:rounded-[4px] pz:bg-muted pz:px-1 pz:text-[10.5px] pz:font-semibold pz:tabular-nums pz:text-muted-foreground pz:ring-1 pz:ring-border",
|
|
46
37
|
children: count
|
|
47
|
-
}) : null
|
|
38
|
+
}) : null,
|
|
39
|
+
/* @__PURE__ */ jsx(ChevronDown, {
|
|
40
|
+
"aria-hidden": "true",
|
|
41
|
+
className: cn("pz:size-3.5 pz:text-muted-foreground pz:shrink-0 pz:transition-transform pz:group-hover/cat:text-foreground", isOpen ? void 0 : "pz:-rotate-90")
|
|
42
|
+
})
|
|
48
43
|
]
|
|
49
44
|
}), isOpen ? /* @__PURE__ */ jsx("div", {
|
|
50
45
|
"data-p0": "catalog-category-section-body",
|