Skip to content

[Bug Report] createSelection: disabled items selectable via multiple-mode v-model #398

Description

@johnleider

Summary

In multiple-selection mode, applying a value through v-model (the apply() path) can select a disabled item, even though select(), unselect(), and toggle() all correctly reject disabled items. This violates the documented contract that a disabled model/item makes all selection operations no-ops.

Location

  • packages/0/src/composables/createModel/index.ts:434apply() browse-fallback adds the resolved id directly (selectedIds.add(id)) with no disabled check.
  • packages/0/src/composables/createSelection/index.ts:260apply() multiple branch adds directly (model.selectedIds.add(id)) with no disabled check.
  • Contrast: createModel.select() (:385-393) guards both instance-level and per-ticket disabled; createSelection.apply() single branch (:266) routes through model.select(next) and is already guarded.
  • Documented contract: createModel/index.ts:355-360 JSDoc — "Disabled model — all selection operations are no-ops".

Impact

Affects multiple-mode createSelection / createGroup / createNested via useProxyModel (multi-Select, Combobox, CheckboxGroup, Treeview, multi-ExpansionPanel). Pushing a disabled item's value into the bound array (reset-to-default, server-restored state) renders the disabled item as selected. useProxyModel does not self-correct — once the id is in selectedIds, its value is reported back. Non-destructive state inconsistency, not a crash; no attacker surface (consumer owns the v-model).

Suggested fix

Guard only the add paths; do not blanket-route through select():

  • createSelection/index.ts:260 — add an inline per-ticket guard (resolve model.get(id), skip if toValue(ticket.disabled)). ⚠️ Do not replace with model.select(id): select clears based on the model's own multiple (false in single-mode-with-override), which would break the existing apply(['A','B'], { multiple: true }) override test (createSelection/index.test.ts:997-1008).
  • createModel/index.ts:434 — routing through select(id) is safe here.
  • Leave the ref-write value-sync path (createModel:419-425) unguarded — it writes into already-selected tickets and must keep syncing.
  • Add a disabled + apply regression test (none currently exists).

Open question (decide before fixing)

Should v-model sync honor instance-level disabled at all, or does disabled gate only user interaction (so programmatic/restored state should mirror regardless)? The per-ticket guard is clearly correct either way; the instance-level question affects whether useProxyModel should revert a consumer's programmatic write.

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions