Skip to content

Add a disabled state that locks the field and omits it from the form - #16

Merged
ulsyemr merged 3 commits into
MehmetCelik4:mainfrom
ulsyemr:feat/disabled-state
Aug 13, 2026
Merged

Add a disabled state that locks the field and omits it from the form#16
ulsyemr merged 3 commits into
MehmetCelik4:mainfrom
ulsyemr:feat/disabled-state

Conversation

@ulsyemr

@ulsyemr ulsyemr commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Add a disabled state

Why

There is currently no way to render an advanced_select that shows its value but refuses to change it. Host apps work around this by wrapping the field in pointer-events-none opacity-50, which only looks disabled: the field is still focusable, still clearable, and — most importantly — still submitted. An update form that renders a read-only field this way happily writes back a value the user was never allowed to touch.

A native <select disabled> has exactly the semantics we want, and the browser already enforces them. This PR gives advanced_select the same contract.

What it does

disabled: true locks the field. The current selection stays on screen — the point is to show the value, not hide it — but the field cannot be opened, searched, or cleared, and its value is left out of the form.

Behaviour Enabled Disabled
Selection visible yes yes
Trigger opens the dropdown yes no — the trigger carries the disabled attribute
Search yes unreachable
Clear (×) control shown when a value is selected hidden
Submitted with the form yes no — hidden inputs carry disabled

Blocking happens at the element level rather than through CSS, so mouse and keyboard are both handled by the browser. That matters for host apps that supply their own ClassMap: the gem's .ui-advanced-select-disabled rules never reach them, so anything enforced only in CSS would silently do nothing.

Usage

<%= advanced_select_tag(
      "record[item_id]",
      id: "record_item_id",
      selected: selected_option,
      options: options,
      placeholder: "Choose an item",
      disabled: true
    ) %>

The root element takes the disabled class from the class map, so host apps can style the locked state alongside everything else:

AdvancedSelect::ClassMap.new(disabled: "bg-gray-100 opacity-50")

Toggling at runtime

The state is a Stimulus value, so it can be flipped after render — from another controller, or from a Turbo Stream that rewrites the attribute:

element.dataset.advancedSelectDisabledValue = "true"

The controller reacts on its own: it disables the hidden inputs, applies the class, hides the clear control, and closes the dropdown if it happens to be open. The same thing is available as a method:

const select = application.getControllerForElementAndIdentifier(element, "advanced-select")

select.disable()
select.enable()

Changes

  • advanced_select_tag(..., disabled: false) — new keyword, passed through to the partial
  • ClassMap::DEFAULTS[:disabled] — new ui-advanced-select-disabled entry
  • _select.html.erbdisabled attribute on the trigger and on every hidden input, disabled class on the root, clear control rendered hidden
  • Stimulus controller — disabled value, disable() / enable(), a disabledValueChanged callback, and guards in open() and clear()
  • Default CSS for the locked trigger, in both the engine stylesheet and the installer template
  • README: a Disabled State section, TOC entry, and the new keyword in the API reference

Also in here

record_advanced_select_events in the system tests now filters by the field under test. It listened on document, so it also picked up the event that the page's eager dependent select broadcasts when it auto-selects on load — which made broadcasts advanced-select:change with the selected value intermittently see two events instead of one. Scoping the recorder to a single field name makes the count deterministic no matter what else the example page does.

Testing

Six new system tests cover the rendered state, form omission, the hidden clear control, and enabling/disabling at runtime. Full suite: 33 runs, 144 assertions, 0 failures, verified green across several random seeds.

Release

Version bumped to 0.1.10.

@ulsyemr
ulsyemr merged commit 55b2914 into MehmetCelik4:main Aug 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant