WIP: Default ‘Select’ dropdown options inside component properties sometimes do not appear

Browser: Chrome 110
OS: macOS / Windows / Linux / etc.
URL: Link to a page that illustrates this issue
Video: Short screen recording that illustrates this issue (free tool: jam.dev)

These are the two cases I’ve encountered:

  1. When binding a property to the condition operator (==, !=, is empty, is not empty) etc. These defaults do not appear as options inside the component property, as they do for other default select fields (such as html tag etc). Manually recreating these options does not appear to work.
  2. When trying to set up a nested component property binding. The child component has a property connected to the image caption select field (attachment, no caption etc). When creating a select property on the parent component and binding it to this child property, the select options do not appear. I haven’t tested whether manually recreating these options works or not. Is this expected behaviour for the current state of property bindings? It’s possible I have misunderstood how property bindings are supposed to work.

Hey @robbiedawson,

Thank you for your report. For the first one, there is already a task opened and you can follow it here: WIP: is/is not empty Condition via Props Doesn't Work Correctly With Dynamic Data - #2 by Matej
Once we fixed it, we will update the forum topic mentioned above.

I was able to replicate the second one, and I’ve created a local task for it. Once it will be fixed, we will update it for a topic.

Thank you,
Matej.

Hey guys. Commenting here because my “duplicate was closed”.

Is there any progress or ETA on this. it has been a while now an this oversight renders nested components as unusabele, unless they ar every basic components.

Thanks.

I’m sure you guys don’t need this but here is a Claue investigation tha tclearly defines the problem and a possible resolution.

Bug Report — Bricks Builder

## Title

Component **Select** property loses its auto-populated options when linked to a **child component’s** property (nested components)

## Environment

- **Bricks version:** 2.3.7

- **Area:** Builder — Components → Component Properties → Select control option resolution

- **Scope:** Builder UI only (client-side Vue app). No server/render involvement.

-–

## Summary

When a component **Select** property is bound directly to a **native element control** whose control defines an options array (e.g. a Heading’s `tag` control → `h1…h6`), the Select auto-populates its options correctly.

When that component is **nested inside a parent component**, and you create a Select property on the **parent** that links to the **child component’s** property, the option list is **not** propagated. The parent’s Select renders **empty**.

The option resolver only knows how to read options from a *native element control*. It has no case for a connection that points at *another component property*, so it never follows the link down to the underlying control’s options array.

-–

## Steps to Reproduce

1. Create a **Component A** containing a single **Heading** element.

2. On Component A, add a **Component Property** of type **Select** and link it to the Heading’s **`tag`** property.

  • :white_check_mark: The Select auto-populates with `h1, h2, h3, h4, h5, h6`. **Works.**

3. Create a **Component B** and place an instance of **Component A** inside it.

4. On Component B, add a **Component Property** of type **Select** and link it to **Component A’s** Select property (created in step 2).

  • :cross_mark: The Select does **not** auto-populate. The option list is empty.

### Expected

The parent (Component B) Select inherits the same option list (`h1…h6`) by following the link chain down to the Heading’s `tag` control.

### Actual

The parent Select has no options.

-–

## Root Cause (technical)

A component property’s binding is stored in `property.connections` as a map `{ [elementId]: [connectionKey] }`. The `connectionKey` takes **two forms**:

1. **Native element control** — a bare control key, e.g. `[“tag”]`.

2. **Component-property link** — a string `“parent:cid_:prop_”` (written by `setPropertyConnection`, parsed elsewhere by the regex `^parent:cid_([^:]+):prop_([^:]+)$`).

The Select option resolver — the **`propertyControlOptions(e)`** method in the builder app (`assets/js/main.min.js`) — only handles **form 1**. Reconstructed logic:

```js

propertyControlOptions(e) {

let n = e?.options || null;

if (n) return n; // static options

if (e?.connections) {

const o = Object.keys(e.connections)\[0\];                          *// target element id*

const a = this.connectedComponent?.elements.find(*el* => el.id === o);

const s = this.$\_getElementConfig(a?.name);                       *// NATIVE element config ONLY*

const l = e.connections\[o\];                                       *// \["tag"\] OR \["parent:cid\_..:prop\_.."\]*

const c = s?.controls?.\[l\]?.options;                              *// reads native control's options*

if (c) n = c;

}

return n;

}

```

`$_getElementConfig(name)` resolves a **registered native element** by name (e.g. `‘heading’`) and exposes `controls.tag.options`. That’s why the single-level case works.

In the nested case, the parent property’s connection target is the **child component instance element** (which has a `.cid` and is **not** a native element), and/or `l` is the link string `“parent:cid_…:prop_…”`. Either way:

- `$_getElementConfig(a.name)` has no matching `controls[l]`, and

- `s.controls[]` is `undefined`,

so `c` is `undefined`, `n` stays empty, and the Select renders with no options. **The resolver never follows the link into the child component’s `properties[]` to read its options (or recurse to its own connection).**

The editing-side twin — the `propertyControl` computed (the `“select”` branch, same file) — has the **identical defect**, so the property-definition dropdown is affected the same way.

-–

## Suggested Fix

Add a recursion step to `propertyControlOptions` (and the matching `propertyControl` computed): when the bound target is another component property (link string `parent:cid_…:prop_…`, or a plain key on a component-instance element with a `.cid`), look up the child component, find the property, and resolve **its** options — preferring a static `options[]` if defined, otherwise following its own connection to a native control’s options, recursing through multiple nesting levels with a cycle guard.

```js

// New helper — resolves a component property’s option list, recursing through

// component-property links until it reaches a native control’s options or a static list.

$_resolveComponentPropertyOptions(cid, propId, seen = {}) {

const guard = cid + ‘:’ + propId;

if (seen[guard]) return null; // circular-link guard

seen[guard] = true;

const comp = this.$_getComponentById(cid);

if (!comp) return null;

const prop = (comp.properties || []).find(p => p.id === propId);

if (!prop) return null;

// 1. Child property defines its own static options.

if (Array.isArray(prop.options) && prop.options.length) return prop.options;

// 2. Child property is itself bound — resolve its connection.

if (prop.connections) {

const o = Object.keys(prop.connections)\[0\];

const keys = prop.connections\[o\] || \[\];

const l = Array.isArray(keys) ? keys\[0\] : keys;

const el = (comp.elements || \[\]).find(*x* => x.id === o);



*// 2a. Bound to a native control inside the child component.*

const opts = this.$\_getElementConfig(el?.name)?.controls?.\[l\]?.options;

if (opts) return opts;



*// 2b. Bound to a deeper component property — recurse.*

const m = typeof l === 'string' && l.match(/^parent:cid\_(\[^:\]+):prop\_(\[^:\]+)$/);

if (m) return this.$\_resolveComponentPropertyOptions(m\[1\], m\[2\], seen);

if (el?.cid) return this.$\_resolveComponentPropertyOptions(el.cid, l, seen);

}

return null;

}

```

Then in `propertyControlOptions`, when the native lookup yields nothing, fall through to the helper:

```js

if (!c) {

let childCid = null, childPropId = null;

const m = typeof l === ‘string’ && l.match(/^parent:cid_([^:]+):prop_([^:]+)$/);

if (m) { childCid = m[1]; childPropId = m[2]; }

else if (a?.cid) { childCid = a.cid; childPropId = l; }

if (childCid && childPropId) c = this.$_resolveComponentPropertyOptions(childCid, childPropId);

}

```

### Edge cases covered

- **Multi-level nesting** (component → component → component): handled by recursion following each link.

- **Circular property links:** guarded by the `seen` set (the existing server-side nesting guard in `process_components_recursive` does not cover the property-link graph).

- **Static vs. derived options:** static author-defined `options[]` on the child takes precedence; only an unstyled child Select falls through to follow its control binding.

- **`multiple` / `searchable`:** only `options` is propagated. If a parent Select should also inherit `multiple`, propagate `prop.multiple` alongside.

-–

## Notes

- The fix belongs in the upstream builder Vue source (the method/computed compile into `assets/js/main.min.js`). Minified symbol names are unstable per build, so this should be patched at source.

- There is currently **no PHP filter or builder-JS hook** that intercepts component-property option resolution, so third parties cannot fix this non-destructively — which is why we’re reporting it upstream rather than working around it.