Skip to content

Enable custom dependent-autocomplete via field registry (4 small API extensions) #29

@stmh

Description

@stmh

Summary

To implement a dependent autocomplete (e.g. Jira project → issue type → user) in a consumer app, we currently ship four small patches to @flowdrop/flowdrop@1.2.2 in the flowdrop-rs frontend. None of the four are present in 1.12.0. This issue proposes them as upstream API extensions so we can drop our patch file.

Issue #28 already proposes solving the dependent-autocomplete use case by adding a params field to AutocompleteConfig directly inside FormAutocomplete. The four extensions below are an orthogonal route: they let consumers register their own dependent-aware component via the field registry, wrapping the built-in FormAutocomplete. Either approach solves the user-facing problem — these extensions are useful for any custom field component that needs sibling form-state, not just dependent autocomplete, so they would still be valuable even if #28 lands.

Use case

In flowdrop-rs the Jira node has these autocomplete fields:

  • account (selects a Jira account secret)
  • project (needs the chosen account to authenticate)
  • issue_type (needs account + project)
  • assignee (needs account + project + a query)

Backend endpoints expect the parent field values as query parameters:

GET /api/flowdrop/jira/issue-types?account={account}&project={project}

The standard FormAutocomplete only appends the search term, so we register a custom component that:

  1. Reads the sibling values out of the form state via Svelte context.
  2. Builds a derived AutocompleteConfig whose url already carries the dependency query params.
  3. Forwards everything to the built-in FormAutocomplete.

To make that work, four pieces of the public API need small tweaks.

Requested changes

1. Expose form values via Svelte context — ConfigForm.svelte

Add a flowdrop:getFormValues context alongside the existing flowdrop:getAuthProvider:

   setContext<() => AuthProvider | undefined>(
     "flowdrop:getAuthProvider",
     () => authProvider,
   );
+  setContext<() => Record<string, unknown>>(
+    "flowdrop:getFormValues",
+    () => configValues,
+  );

Why: Without this, a registered field component has no general-purpose way to observe sibling values reactively. This is the only piece that requires source from ConfigForm, and it's useful for any cross-field component (not just autocomplete).

2. Consult fieldComponentRegistry for autocomplete fields — FormField.svelte

FormFieldLight already routes autocomplete fields through the registry (per the original work behind #11). The heavier FormField does not — it always falls through to the built-in FormAutocomplete. Mirror the registry consultation there:

+ import { fieldComponentRegistry } from "../../form/fieldRegistry.js";
  ...
+ const registeredComponent = $derived(
+   fieldComponentRegistry.resolveFieldComponent(schema),
+ );
  ...
+ {:else if fieldType === "autocomplete" && schema.autocomplete && registeredComponent}
+   <registeredComponent.component
+     id={fieldKey}
+     value={autocompleteValue}
+     autocomplete={schema.autocomplete}
+     dependencies={schema.dependencies}
+     placeholder={schema.placeholder ?? ""}
+     {required}
+     ariaDescribedBy={descriptionId}
+     disabled={isReadOnly}
+     onChange={(val) => onChange(val)}
+   />
  {:else if fieldType === "autocomplete" && schema.autocomplete}
    <FormAutocomplete ... />

Why: Consumers that use FormField (the rich variant) cannot currently override autocomplete with a registered component.

3. Pass autocomplete + dependencies to registered components — FormFieldLight.svelte

The registered-component invocation in FormFieldLight does not forward autocomplete or dependencies:

   <registeredComponent.component
     ...
     variables={schema.variables}
     placeholderExample={schema.placeholderExample as string | undefined}
+    autocomplete={schema.autocomplete}
+    dependencies={schema.dependencies}
     onChange={(val: unknown) => onChange(val)}
   />

Why: Without this a registered autocomplete component has no idea what URL to fetch from. Closely related to #11, but the autocomplete-config plumbing wasn't applied to this registered branch.

4. Export FormAutocomplete as a subpath — package.json

   "./form": {
     "types": "./dist/form/index.d.ts",
     "svelte": "./dist/form/index.js",
     "default": "./dist/form/index.js"
   },
+  "./form/autocomplete": {
+    "types": "./dist/components/form/FormAutocomplete.svelte.d.ts",
+    "svelte": "./dist/components/form/FormAutocomplete.svelte",
+    "default": "./dist/components/form/FormAutocomplete.svelte"
+  },
   "./form/code": { ... },

Why: The whole point of (1)–(3) is to let a consumer wrap FormAutocomplete rather than reimplement type-ahead, debouncing, label caching, etc. Without a public export there is no supported way to import it.

Schema shape consumers would use

These changes accept (but do not interpret) a dependencies map on the field schema:

{
  "type": "string",
  "format": "autocomplete",
  "autocomplete": { "url": "/api/jira/issue-types", "fetchOnFocus": true },
  "dependencies": { "account": "account", "project": "project" }
}

Mapping: URL query-param name → sibling field name. The registered component resolves dependency values via getContext('flowdrop:getFormValues') and appends them to the URL.

Reference implementation

The dependent-autocomplete wrapper we ship (FormDependentAutocomplete.svelte, ~170 LOC) is happy to be contributed back if helpful, but the proposal here is just the four small API hooks — consumers can supply their own component logic.

Test plan / impact

  • All four changes are additive: no existing prop, export, or context is removed or renamed.
  • Existing consumers that don't register a custom autocomplete component get unchanged behaviour (the new {:else if … && registeredComponent} branch only activates when something is registered).
  • (1) is a new context only — uncontextual consumers ignore it.
  • (4) is a new subpath only — ./form etc. are unchanged.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions