Skip to content

State paths for nested fields, translatable fields without a translatable model, and isInteger() (for javaabu/meta) #16

Closed

Description

@n4ff4h

javaabu/meta (Javaabu/meta#3, #9) defines meta fields with stencil fields:

MetaFields::for(Post::class, [
    TextInput::make('subtitle')->maxLength(255)->translatable(),
    Checkbox::make('featured'),
    TextInput::make('views')->integer(),
]);

These fields then go into the model's own form (post-types' post form, for example) with their state nested under meta:

  • input names meta[subtitle]
  • state and validation keys meta.subtitle
  • the value is read with data_get($post, 'meta.subtitle')
  • it's saved through fill(['meta' => [...]])

HasMeta takes care of the model side. What stencil is missing:

1. Nest a container's fields under a state path

Group::make()->statePath('meta')->schema(MetaFields::fields($post))

The fields inside would get:

  • getStatePath() / getInputName() / getId(): meta.subtitle / meta[subtitle] / meta-subtitle
  • validation rules and attributes keyed meta.subtitle
  • hydration, old input, dehydrate() and fillModel() (root attribute meta) all using the nested path

This is Filament's statePath() on layout components. Right now a field's path is fixed by make($name), and there's no public way to rename or re-path a field someone else created. A public Field::name() setter (or withName()) would also unblock this, but statePath() is the cleaner API. Conditions inside the group could keep taking absolute paths (visibleWhen('meta.featured', true)).

2. Translatable fields whose record isn't a translatable model

Translatable meta values are stored per locale by javaabu/meta, not as attributes of a Translatable model. So Translatable::supports($post, 'meta.subtitle') is false, and ->translatable() falls back to a plain field.

A hook would cover this: something on the form or record that says a state path is translatable. For example, a contract the record can implement:

interface ResolvesTranslatableFields
{
    public function isTranslatableField(string $state_path): bool;
}

Stencil would then expand that field with Translatable::inputs() and expand its rules itself (Translatable::rules() also checks supports()).

Nested paths also need bracketed input names: meta[subtitle_dv], not meta.subtitle_dv, because PHP turns dots in input names into underscores. The state and dehydrated keys would be meta.subtitle_dv (or meta.subtitle.dv; meta can take either, so pick whatever is natural for stencil).

3. Small public getters

  • TextInput::isInteger(), so meta can store ->integer() inputs in its integer column. Right now only isNumeric() is public.
  • Field::isDeclaredTranslatable(), meaning ->translatable() was called (evaluated), whether or not translatable is installed. Meta uses it to store per-locale values.

Until these land, javaabu/meta keeps the definitions, storage, query scopes and types working, and labels its form integration blocked. I'll switch to these APIs as soon as they're on main.

Activity

  1. added
    next-genNext-gen Javaabu packages
    cross-packageRequested by or affecting another next-gen package
    on Nov 5, 2026
  2. n4ff4h commented on Nov 5, 2026

    @n4ff4h
    CollaboratorAuthor

    Update from javaabu/meta: I've narrowed what meta needs from this issue.

    • Part 2 (translatable fields without a translatable model): not needed. Meta now uses settings v2's approach. A meta form binds a never-saved, JSON-translatable record (Javaabu\Meta\Support\Translatable\TranslatableMetaRecord), so ->translatable() works with stencil as it is today. To find out whether a field was declared translatable, meta asks a copy of it in a probe form. A public isDeclaredTranslatable() would still be nicer, but it's optional.
    • Part 3 (TextInput::isInteger()): still wanted. Without it, TextInput::make('views')->integer() is stored as a float unless the app adds ->metaType('integer'). Meta already calls isInteger() when the method exists.
    • Part 1 (statePath() on containers): nice to have. Without it, meta fields render in their own MetaForm (or ->embedded() next to the record's form), with input names that aren't prefixed. With it, MetaFields could also return fields nested under meta. for the record's own form.

    Nothing in meta is blocked on this issue now.

  3. n4ff4h commented on Nov 5, 2026

    @n4ff4h
    CollaboratorAuthor

    Done in 3e3ab27 (CI green on 8881bfc).

    1. statePath() on any component nests the fields inside it. Group::make()->statePath('meta')->schema([...]) gives:

      • getStatePath() → meta.subtitle, getInputName() → meta[subtitle], getId() → meta-subtitle, and the node key meta.subtitle;
      • hydration from data_get($record, 'meta.subtitle') and from old input;
      • rules and attributes keyed by meta.subtitle;
      • validate()/dehydrate() → ['meta' => [...]], and fillModel() fills the root attribute meta.

      Paths nest (meta.seo.title). Look fields up by their full path ($form->getField('meta.subtitle')); getName() is still subtitle. On a field, ->statePath('x') replaces the path its name gives. Conditions take absolute paths (visibleWhen('meta.featured', true)).

    2. Javaabu\Stencil\Contracts\ResolvesTranslatableFields (isTranslatableField(string $state_path): bool). It works on the record or on a form class, and is asked instead of Translatable::supports() (translatable must still be installed and enabled). Then:

      • The field expands with Translatable::inputs($state_path, $record).
      • Stencil expands the rules itself: the primary locale as written, the others nullable.
      • Values are read with data_get($record, 'meta.subtitle_dv').
      • State, rule and validated keys are dot paths (meta.subtitle_dv), and input names are bracketed (meta[subtitle_dv]).
      • In the JSON, each translatable input has name (bracketed) and statePath (dot).
    3. TextInput::isInteger() and Field::isDeclaredTranslatable(), which is true when ->translatable() was called and its condition holds, whether or not the field expands.

    Docs: layout.md#nesting-state and translatable.md#records-that-arent-translatable-models.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cross-packageRequested by or affecting another next-gen packagenext-genNext-gen Javaabu packages

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions