feature/FE-2810#87
Open
RhodriJonesUpmind wants to merge 12 commits into
Open
Conversation
Interactive components can now pick up `disabled` and `readonly` state from any ancestor via provide/inject, eliminating the need to wire those props at every call site. Adds `DISABLED_KEY`/`useDisabled`/`provideDisabled` and the same for readonly in `src/utils/`. Each composable returns a resolved `ComputedRef<boolean>` that ORs the local source with any ancestor-provided value. Either key can be provided at any level (app shell, layout, page, section); the closest ancestor wins. Helper components `<DisabledProvider>` and `<ReadonlyProvider>` make declarative scoping ergonomic. Wired into 22 disabled-aware components (button, button-group, input, input-password, textarea, number-field, select, select-grouped, select-cards, combobox, autocomplete, checkbox, checkbox-cards, checkbox-group, radio-cards, radio-cards-collapsible, radio-group, switch, toggle, command, search, link) and 3 readonly-aware (input, input-password, textarea). Select-family and number-field readonly deferred — the underlying radix-vue primitives don't expose `readonly`. Existing `disabled`/`readonly` props continue to work standalone; the cascade is OR'd with the prop, so behaviour is unchanged when no provider exists.
Input had no visual disabled treatment — functionally `:disabled` on the native input worked, but the container showed no opacity dim, cursor change, or hover suppression. The cascade landed in 2948dd6 (FE-2810) exposed this because the input now gets disabled more often via ambient context. Mirrors the radio-cards pattern: sets `data-disabled` on the container from the resolved disabled state, and extends the container variants with `data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50`
Form's processing/disabled state only reached JsonForms-rendered controls via the `:enabled` prop. Non-JsonForms slot content (e.g. `<Manage>` rendering `<RadioCardsCollapsible>` for address selection) only consumed the ancestor disabled cascade, missing Form's own processing signal — so during a form submit, text inputs stayed locked but the address cards briefly enabled, creating a visible desync. Form now calls `provideDisabled(isDisabled)` after its existing `useDisabled(...)` resolution, becoming a disabled boundary: descendants inject Form's resolved state (cascade ∪ disabled ∪ processing) rather than the cascade alone. Standard pattern — Vuetify's `<v-form>` and FormKit's `<FormKit>` do the same for the same reason.
Replaces the `:data-disabled="isDisabled || undefined"` + Tailwind `data-[disabled]:*` pattern with a proper CVA `isDisabled` variant on Input, RadioCards, and RadioCardsCollapsible item wrappers. Disabled visual now flows: `useDisabled()` → `meta.isDisabled` → CVA variant → classes. No DOM data-attribute, no Tailwind arbitrary selector, no `|| undefined` idiom. Matches how `Button.ce.vue` handles its own disabled visual and aligns with the "classes live in `*.config.ts` + `useStyles`" convention.
`DisabledProvider` / `ReadonlyProvider` were slot-only wrappers around `provideDisabled()` / `provideReadonly()` for template-scoped cascade provisioning. Zero usages found across packages/ and apps/ — every actual provide site (UpmPage, Form) calls the function directly from `<script setup>`. Removes: - `packages/ui/src/ui/disabled-provider/` - `packages/ui/src/ui/readonly-provider/` - Two top-level `export *` re-exports Trivial to re-add (~10 LOC per component) if a template-scoped use case ever materialises — keeping surface area minimal for now.
Earlier added Form `provideDisabled(isDisabled)` on the assumption
that non-JsonForms slot content (RadioCardsCollapsible in Manage)
needed Form's processing signal via the cascade. That assumption
was wrong:
- Manage renders the list and edit-form as siblings — the radio
cards aren't inside any `<Form>`.
- JsonForms-rendered controls inside Form already get disabled via
`:enabled="!meta.isDisabled"` → renderer's
`appliedOptions.disabled` → Input's own `useDisabled` ORs with
the cascade.
- Default action buttons read `meta.isProcessing` explicitly.
- new shimmer-mask utility: alternating CSS-mask sweep over currentColor
- loading implies disabled (Button parity via useDisabled)
- swap to span + click guard while disabled so cursor-wait/not-allowed
show without pointer-events hacks
* main: fix ui-config cva type error feat(ui): forward Input ui-config to InputItems for renderer-owned icon styling fix(ButtonProps): allow 'link' as a variant option
BreadcrumbItem gains `loading`, forwarded to the clickable Link so a
single crumb can shimmer while its navigation is in flight. The Link
now also invokes `item.handler` on click — the field existed on the
public type but was never wired, so handler-driven consumers
(useBreadcrumbs onSelect / pending tracking) can finally react to
clicks. Link's loading guard already blocks repeat clicks.
…g flag `:loading="meta.isProcessing"` came after `v-bind="action"`, clobbering any `loading` the caller passed on the action.
…ith disabled (FE-2810) Form forwarded its raw `readonly` prop straight to JsonForms while `disabled` went through `useDisabled` (local prop OR ancestor-provided). Route `readonly` through `useReadonly(() => props.readonly)` too, so both resolve the same cascade-aware way. No behaviour change today (nothing provides readonly yet), but Form is now consistent and ready to inherit an ancestor `provideReadonly`.
dominicpedro
requested changes
Jun 16, 2026
dominicpedro
left a comment
Contributor
There was a problem hiding this comment.
in general i would prefer the const isDisabled = to jsut be const disabled = as the readonly ref, and rthen using isDisabled in meta. i dont like is/has/etc as standalone computeds.
| :loading="item.loading" | ||
| color="muted" | ||
| :uiConfig="{ link: { items: '[&>i]:p-[3px]' } } as any" | ||
| @click="item.handler?.()" |
Contributor
There was a problem hiding this comment.
this need s a proper onClick so we dont fire the handler when disabled. like we do in link.. i nfact wh yare we not using the Link Action?
| * Provides a disabled signal to descendants. Accepts a plain boolean or a ref. | ||
| */ | ||
| export const provideDisabled = (value: MaybeRef<boolean>): void => { | ||
| provide(DISABLED_KEY, value); |
Contributor
There was a problem hiding this comment.
why do we need to use provide if we are using a composible?
| * Provides a readonly signal to descendants. Accepts a plain boolean or a ref. | ||
| */ | ||
| export const provideReadonly = (value: MaybeRef<boolean>): void => { | ||
| provide(READONLY_KEY, value); |
Contributor
There was a problem hiding this comment.
same here. i dont think we need provide inject if we have a composible. we can jsut create a singleton and expose i nthe return
useDisabled/useReadonly used provide/inject, so the cascade could only be seeded
from inside a component setup(). Replace the InjectionKey with a module-level
singleton: each composable holds a Set of contributed sources and resolves
`local source || any source`. Works outside component setup, no provider ancestor.
- provideDisabled/provideReadonly -> setDisabled/setReadonly (register a source,
return a stop fn, auto-clean on scope dispose); DISABLED_KEY/READONLY_KEY removed.
- useDisabled/useReadonly signatures unchanged, so consumer call sites are untouched.
- A Set (not a single ref) so multiple contributors OR together instead of
clobbering; the cascade is now global rather than subtree-scoped.
Also surface isReadonly in Form's meta and FormMeta, alongside isDisabled, so the
readonly state is exposed the same is-prefixed way (review consistency).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.