# Changelog

## [0.9.0-dev1] - 2025-02-02

### Breaking Changes

**Major architectural refactoring: Form/Layout separation**

This release introduces a clean separation between form logic (data and validation) and form presentation (layout and rendering). This is a **breaking change** but provides much better flexibility and follows Textual best practices.

#### What Changed

1. **RenderedForm removed** - Forms are no longer widgets themselves
2. **FormLayout introduced** - New base class for creating form layouts
3. **Form.render() now returns FormLayout** - Not a breaking API change, but returns a different type
4. **BoundField is now callable** - WTForms-style widget rendering: `field()`

#### Migration Guide

**Before (v0.8.x):**
```python
form = MyForm(data={'name': 'Alice'})
app.mount(form.render())  # Returns RenderedForm (a widget)

# Message handling
@on(Form.Submitted)
def handle_submit(self, event):
    data = event.form.get_data()  # event.form was RenderedForm
```

**After (v0.9.x):**
```python
form = MyForm(data={'name': 'Alice'})
app.mount(form.render())  # Returns DefaultFormLayout (a widget)

# Message handling - slightly different
@on(Form.Submitted)
def handle_submit(self, event):
    data = event.form.get_data()  # event.form is now the Form instance
    # event.layout is the FormLayout widget
```

#### New Capabilities

**1. Custom Layouts**

You can now create custom layouts by subclassing `FormLayout`:

```python
from textual_wtf import FormLayout
from textual.containers import Horizontal, Vertical

class TwoColumnLayout(FormLayout):
    def compose_form(self):
        with Horizontal():
            with Vertical():
                yield self.render_field('first_name')
                yield self.render_field('email')
            with Vertical():
                yield self.render_field('last_name')
                yield self.render_field('phone')
        
        # Render submit buttons
        # ... (see DefaultFormLayout for button example)

# Use it
form = ContactForm()
app.mount(TwoColumnLayout(form))
```

**2. Same Form, Different Layouts**

Since Form is just data, you can render the same form instance with different layouts:

```python
form = UserProfileForm()

# Desktop view
desktop_panel.mount(DefaultFormLayout(form))

# Mobile view (after unmounting desktop)
mobile_panel.mount(CompactFormLayout(form))
```

**3. Callable BoundFields (WTForms-style)**

Fields can now be called to get their widgets:

```python
class CustomLayout(FormLayout):
    def compose_form(self):
        # Call field to get widget
        yield self.form.fields['name']()
        
        # Or with configuration
        yield self.form.fields['email'](placeholder="Enter email")
```

**4. Duplicate Rendering Prevention**

The layout system prevents accidentally rendering the same field twice:

```python
class BadLayout(FormLayout):
    def compose_form(self):
        yield self.render_field('name')
        yield self.render_field('name')  # Raises DuplicateFieldRenderError
```

### Added

- `FormLayout` - Base class for custom form layouts
- `DefaultFormLayout` - Default vertical layout (replicates old RenderedForm behavior)
- `DuplicateFieldRenderError` - Exception raised when field rendered twice
- `BoundField.__call__()` - Make fields callable to get widgets
- `Field.create_widget(override_kwargs)` - Accept optional kwargs for widget configuration

### Changed

- `Form` is no longer a widget - use `form.render()` to get a FormLayout widget
- `Form.Submitted` message now has `.layout` (FormLayout) and `.form` (Form) attributes
- `Form.Cancelled` message now has `.layout` (FormLayout) and `.form` (Form) attributes
- Validation logic moved from Form to FormLayout

### Removed

- `RenderedForm` class - replaced by `DefaultFormLayout`
- `BaseForm.render_type` parameter - use `form.render(layout_class=...)` instead
- `BaseForm.validate()` method - now on FormLayout

### Internal

- Better separation of concerns: Form = business logic, Layout = presentation
- Layouts are composable Textual widgets
- Forms are pure Python objects (no widget inheritance)

## [0.8.0] - Previous Release

(See previous changelog entries...)
