textual-wtf 0.8.0__py3-none-any.whl → 0.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
textual_wtf/__init__.py CHANGED
@@ -21,6 +21,7 @@ from .fields import (
21
21
  TextField,
22
22
  )
23
23
  from .forms import Form
24
+ from .layouts import FormLayout, DefaultFormLayout
24
25
  from .validators import (
25
26
  EvenInteger,
26
27
  Palindromic,
@@ -51,6 +52,9 @@ __all__ = [
51
52
  "TextField",
52
53
  # Forms
53
54
  "Form",
55
+ # Layouts
56
+ "FormLayout",
57
+ "DefaultFormLayout",
54
58
  # Validators
55
59
  "EvenInteger",
56
60
  "Palindromic",
@@ -63,5 +67,5 @@ __all__ = [
63
67
  "FormSelect",
64
68
  "WidgetRegistry",
65
69
  # Structural
66
- "__version__,"
70
+ "__version__",
67
71
  ]
@@ -155,14 +155,21 @@ class BoundField:
155
155
  # Methods - Delegate to Field for logic
156
156
  # ========================================================================
157
157
 
158
- def create_widget(self) -> Widget:
158
+ def create_widget(self, **kwargs) -> Widget:
159
159
  """
160
160
  Create widget using field configuration
161
161
 
162
+ Args:
163
+ **kwargs: Additional keyword arguments to pass to widget constructor
164
+
162
165
  Returns:
163
166
  Widget instance configured from Field
164
167
  """
165
- widget = self.field.create_widget()
168
+ # Merge kwargs with field's widget_kwargs
169
+ merged_kwargs = {**self.field.widget_kwargs, **kwargs}
170
+
171
+ # Create widget with merged kwargs
172
+ widget = self.field.create_widget(widget_kwargs=merged_kwargs)
166
173
  self._widget_instance = widget
167
174
  # Set back-reference
168
175
  widget.bound_field = self
@@ -170,6 +177,44 @@ class BoundField:
170
177
  widget.field = self
171
178
  return widget
172
179
 
180
+ def __call__(self, **kwargs) -> Widget:
181
+ """
182
+ Callable interface for rendering fields (WTForms-style)
183
+
184
+ Creates and returns the widget, configured with any additional
185
+ keyword arguments. This is the primary way to render fields in
186
+ custom layouts.
187
+
188
+ Args:
189
+ **kwargs: Keyword arguments to pass to widget constructor
190
+ (e.g., placeholder="Enter name", disabled=True)
191
+
192
+ Returns:
193
+ Widget instance configured from Field and kwargs
194
+
195
+ Example:
196
+ # In a custom layout's compose_form():
197
+ yield self.form.name(placeholder="Enter your name")
198
+ yield self.form.email(disabled=True)
199
+ """
200
+ # Track rendering if we're inside a layout
201
+ if hasattr(self.form, '_current_layout'):
202
+ self.form._current_layout._track_field_render(self.name)
203
+
204
+ # Create widget with kwargs if not already created
205
+ if self._widget_instance is None:
206
+ widget = self.create_widget(**kwargs)
207
+ else:
208
+ # Widget already exists - for now, just return it
209
+ # (Could optionally update widget properties from kwargs)
210
+ widget = self._widget_instance
211
+
212
+ # Sync initial value to widget
213
+ if self._value is not None:
214
+ widget.value = self.field.to_widget(self._value)
215
+
216
+ return widget
217
+
173
218
  def to_python(self, value: Any) -> Any:
174
219
  """Convert widget value to Python value (delegated to Field)"""
175
220
  return self.field.to_python(value)
@@ -65,7 +65,7 @@ class AdvancedFormScreen(Screen):
65
65
  align: center middle;
66
66
  }
67
67
 
68
- RenderedForm {
68
+ DefaultFormLayout {
69
69
  width: 60;
70
70
  height: auto;
71
71
  max-height: 90%;
@@ -114,7 +114,7 @@ class AdvancedFormScreen(Screen):
114
114
 
115
115
  def reset_form(self):
116
116
  """Clear form and create fresh one"""
117
- old_form = self.query_one("RenderedForm")
117
+ old_form = self.query_one("DefaultFormLayout")
118
118
  old_form.remove()
119
119
 
120
120
  self.form = ContactForm(title="Contact Information")
@@ -63,7 +63,7 @@ class BasicFormScreen(Screen):
63
63
  def reset_form(self):
64
64
  """Clear form and create fresh one"""
65
65
  # Remove old form
66
- old_form = self.query_one("RenderedForm")
66
+ old_form = self.query_one("DefaultFormLayout")
67
67
  old_form.remove()
68
68
 
69
69
  # Create and mount new form
@@ -73,7 +73,7 @@ class ShopScreen(Screen):
73
73
  align: center middle;
74
74
  }
75
75
 
76
- RenderedForm {
76
+ DefaultFormLayout {
77
77
  width: 80;
78
78
  height: 100%;
79
79
  max-height: 100%;
@@ -138,7 +138,7 @@ class ShopScreen(Screen):
138
138
 
139
139
  def reset_form(self):
140
140
  """Clear form and create fresh one"""
141
- old_form = self.query_one("RenderedForm")
141
+ old_form = self.query_one("DefaultFormLayout")
142
142
  old_form.remove()
143
143
 
144
144
  self.form = OrderForm(title="Order Form - Composed from Reusable Forms")
@@ -79,7 +79,7 @@ class ShopScreen(Screen):
79
79
  align: center middle;
80
80
  }
81
81
 
82
- RenderedForm {
82
+ DefaultFormLayout {
83
83
  width: 80;
84
84
  height: 100%;
85
85
  max-height: 100%;
@@ -147,7 +147,7 @@ class ShopScreen(Screen):
147
147
 
148
148
  def reset_form(self):
149
149
  """Clear form and create fresh one"""
150
- old_form = self.query_one("RenderedForm")
150
+ old_form = self.query_one("DefaultFormLayout")
151
151
  old_form.remove()
152
152
 
153
153
  self.form = OrderForm(title="Order Form - Composed from Reusable Forms")
@@ -60,7 +60,7 @@ class RegistrationScreen(Screen):
60
60
  border: solid blue;
61
61
  }
62
62
 
63
- RenderedForm {
63
+ DefaultFormLayout {
64
64
  width: 60;
65
65
  height: auto;
66
66
  }
@@ -110,7 +110,7 @@ class RegistrationScreen(Screen):
110
110
 
111
111
  def reset_form(self):
112
112
  """Clear form and create fresh one"""
113
- old_form = self.query_one("RenderedForm")
113
+ old_form = self.query_one("DefaultFormLayout")
114
114
  old_form.remove()
115
115
  self.form = RegistrationForm(title="Create Account")
116
116
  self.query_one("VerticalScroll").mount(self.form.render())
textual_wtf/fields.py CHANGED
@@ -83,13 +83,16 @@ class Field(ABC):
83
83
  from .bound_fields import BoundField
84
84
  return BoundField(self, form, name, initial)
85
85
 
86
- def create_widget(self) -> Widget:
86
+ def create_widget(self, widget_kwargs: Optional[dict] = None) -> Widget:
87
87
  """
88
88
  Factory method to create configured widget
89
89
 
90
90
  Note: This creates the widget but doesn't store it.
91
91
  The BoundField is responsible for storing widget instances.
92
92
 
93
+ Args:
94
+ widget_kwargs: Optional additional kwargs to merge with self.widget_kwargs
95
+
93
96
  Returns:
94
97
  Configured widget instance
95
98
  """
@@ -99,7 +102,10 @@ class Field(ABC):
99
102
  f"or pass widget parameter"
100
103
  )
101
104
 
105
+ # Merge base kwargs with runtime kwargs
102
106
  kwargs = self.widget_kwargs.copy()
107
+ if widget_kwargs:
108
+ kwargs.update(widget_kwargs)
103
109
 
104
110
  # Pass validators to widget if it supports them
105
111
  if hasattr(self.widget_class, '__init__'):
textual_wtf/forms.py CHANGED
@@ -1,11 +1,9 @@
1
1
  """Form metaclass and base classes"""
2
2
  import copy
3
3
  from typing import Dict, Any, Optional, List
4
- from textual import on
5
- from textual.containers import Vertical, Center, Horizontal, VerticalScroll
6
- from textual.widgets import Button, Static, Label
7
4
  from textual.message import Message
8
-
5
+ from textual.containers import Center
6
+ from textual.widgets import Static
9
7
  from .fields import Field
10
8
  from .exceptions import FieldError, AmbiguousFieldError, ValidationError
11
9
 
@@ -100,192 +98,71 @@ class FormMetaclass(type):
100
98
  return new_class
101
99
 
102
100
 
103
- class RenderedForm(VerticalScroll):
104
- """Rendered form widget that displays fields and buttons"""
105
-
106
- DEFAULT_CSS = """
107
- RenderedForm {
108
- keyline: thin green;
109
- }
110
-
111
- Vertical {
112
- margin: 1;
113
- }
114
-
115
- #form-title {
116
- background: blue;
117
- height: auto;
118
- margin: 1;
119
- }
120
-
121
- .subform-title {
122
- background: white;
123
- color: black;
124
- height: auto;
125
- padding: 0 1;
126
- margin: 1 0 0 0;
127
- }
128
-
129
- .form-field {
130
- height: auto;
131
- }
132
-
133
- .form-error {
134
- color: red;
135
- width: 1fr;
136
- }
137
-
138
- #buttons {
139
- height: auto;
140
- align: center middle;
141
- margin: 0;
142
- }
143
-
144
- #outer-buttons {
145
- height: auto;
146
- }
147
-
148
- Input {
149
- height: auto;
150
- }
151
-
152
- TextArea {
153
- height: 6;
154
- }
155
- """
156
-
157
- def __init__(self, form, data: Optional[Dict[str, Any]] = None,
158
- field_order: Optional[List[str]] = None, id=None):
159
- """
160
- Initialize rendered form
161
- """
162
- super().__init__(id=id, **form.kwargs)
163
- self.form = form
164
- self.fields = form.fields
165
- self.data = data
166
- self.field_order = field_order
167
-
168
- if data is not None:
169
- self.set_data(data)
170
-
171
- def compose(self):
172
- """Compose the form UI"""
173
- # Optional title
174
- if self.form.title is not None:
175
- yield Vertical(
176
- Center(Static(f"---- {self.form.title} ----")),
177
- id="form-title"
178
- )
179
-
180
- # Track which subforms we've already rendered headers for
181
- rendered_subforms = set()
182
-
183
- # Render each field
184
- for name, field in self.form.fields.items():
185
- # Check if this field is part of a composed subform with a title
186
- metadata = self.form._composition_metadata.get(name)
187
- if metadata and metadata.get('title'):
188
- subform_id = metadata['composed_from']
189
-
190
- # Render subform title once per subform
191
- if subform_id not in rendered_subforms:
192
- yield Static(metadata['title'], classes="subform-title")
193
- rendered_subforms.add(subform_id)
194
-
195
- with Vertical(classes="form-field"):
196
- if field.label:
197
- yield Label(field.label)
198
- yield field.widget
199
-
200
- # Set initial data if provided
201
- if self.data and name in self.data:
202
- field.value = self.data[name]
203
-
204
- # Submit/Cancel buttons
205
- yield Vertical(
206
- Horizontal(
207
- Button("Cancel", id="cancel"),
208
- Button("Submit", id="submit", variant="primary"),
209
- id="buttons"
210
- ),
211
- id="outer-buttons"
212
- )
213
-
214
- def get_data(self) -> Dict[str, Any]:
215
- """Get current form data"""
216
- return self.form.get_data()
217
-
218
- def set_data(self, data: Dict[str, Any]):
219
- """Set form data"""
220
- return self.form.set_data(data)
221
-
222
- async def validate(self):
223
- """Validate all form fields"""
224
- return await self.form.validate()
225
-
226
- @on(Button.Pressed, "#submit")
227
- async def submit_pressed(self, event: Button.Pressed) -> None:
228
- """Handle submit button press"""
229
- if await self.validate():
230
- self.post_message(Form.Submitted(self))
231
- else:
232
- self.app.notify("Please fix the errors before submitting", severity="error")
233
-
234
- @on(Button.Pressed, "#cancel")
235
- async def cancel_pressed(self, event: Button.Pressed) -> None:
236
- """Handle cancel button press"""
237
- self.post_message(Form.Cancelled(self))
238
101
 
239
102
 
240
103
  class BaseForm:
241
104
  """Base form class without metaclass"""
242
105
 
106
+ # Default layout class to use when rendering
107
+ layout_class = None # Will be set to DefaultFormLayout after import
108
+
243
109
  def __init__(self, *children, data: Optional[Dict[str, Any]] = None,
244
110
  field_order: Optional[List[str]] = None,
245
- title: Optional[str] = None, render_type=RenderedForm, **kwargs):
111
+ title: Optional[str] = None,
112
+ layout_class=None,
113
+ **kwargs):
246
114
  """
247
115
  Initialize form
248
-
116
+
249
117
  Creates BoundField instances from class-level Field definitions.
250
118
  This is much faster than deep copying and enables thread-safe
251
119
  Form class reuse.
252
-
120
+
253
121
  Args:
254
122
  data: Initial data dict
255
123
  field_order: Custom field ordering
256
124
  title: Form title
257
- render_type: Custom renderer class
258
- **kwargs: Additional kwargs for renderer
125
+ layout_class: Custom layout class (overrides class-level layout_class)
126
+ **kwargs: Additional kwargs for layout
259
127
  """
260
128
  self.data = data
261
129
  self.children = children
262
130
  self.field_order = field_order
263
131
  self.title = title
264
132
  self.kwargs = kwargs
265
- self.render_type = render_type
133
+
134
+ # Layout configuration
135
+ if layout_class is not None:
136
+ self._layout_class = layout_class
137
+ elif self.layout_class is not None:
138
+ self._layout_class = self.layout_class
139
+ else:
140
+ # Will use DefaultFormLayout (imported at module level)
141
+ from .layouts import DefaultFormLayout
142
+ self._layout_class = DefaultFormLayout
266
143
 
267
144
  # Create BoundFields from class-level Field definitions
268
145
  # NO MORE DEEP COPY - just create lightweight BoundField wrappers
269
146
  self.bound_fields: Dict[str, 'BoundField'] = {}
270
-
147
+
271
148
  for name, field in self._base_fields.items():
272
149
  # Get initial value from data if provided
273
150
  initial = data.get(name) if data else None
274
151
  # Create BoundField (holds runtime state)
275
152
  bound_field = field.bind(self, name, initial)
276
153
  self.bound_fields[name] = bound_field
277
-
154
+
278
155
  # Set as attribute for direct access (form.fieldname)
279
156
  setattr(self, name, bound_field)
280
157
 
281
158
  # Apply custom field ordering if provided
282
159
  self.order_fields(self.field_order)
283
-
160
+
284
161
  @property
285
162
  def fields(self) -> Dict[str, 'BoundField']:
286
163
  """
287
164
  Backward compatibility: alias for bound_fields
288
-
165
+
289
166
  Returns bound_fields so existing code using form.fields continues to work.
290
167
  """
291
168
  return self.bound_fields
@@ -299,7 +176,7 @@ class BaseForm:
299
176
  field = self.get_field(name)
300
177
  if field:
301
178
  return field
302
-
179
+
303
180
  raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
304
181
 
305
182
  @classmethod
@@ -371,20 +248,24 @@ class BaseForm:
371
248
  f"Use the full qualified name to disambiguate."
372
249
  )
373
250
 
374
- def render(self, id=None) -> RenderedForm:
375
- """Render the form as a Textual widget"""
376
- # Create widgets for all fields
377
- for name, field in self.fields.items():
378
- field.widget = field.create_widget()
251
+ def render(self, id=None):
252
+ """
253
+ Render the form as a Textual widget
254
+
255
+ Creates a layout instance using the configured layout_class.
256
+ During rendering, sets self._current_layout so that field()
257
+ calls can track which fields have been rendered.
258
+
259
+ Returns:
260
+ Layout instance (e.g., DefaultFormLayout or custom layout)
261
+ """
262
+ # Create layout instance
263
+ layout = self._layout_class(self, id=id, **self.kwargs)
264
+
265
+ # Store layout reference so BoundField.__call__ can track rendering
266
+ self._current_layout = layout
379
267
 
380
- # Create and return rendered form
381
- self.rform = self.render_type(
382
- self,
383
- id=id,
384
- data=self.data,
385
- field_order=self.field_order
386
- )
387
- return self.rform
268
+ return layout
388
269
 
389
270
  async def validate(self):
390
271
  """Validate all form fields"""
@@ -403,14 +284,14 @@ class BaseForm:
403
284
  result = False
404
285
  # Display errors
405
286
  for msg in vr.failure_descriptions:
406
- container.mount(Center(Static(msg, classes="form-error")))
287
+ container.mount(Center(Static(msg), classes="form-error"))
407
288
 
408
289
  # Field-level validation (required, custom Field validators)
409
290
  try:
410
291
  field.clean(field.value)
411
292
  except ValidationError as e:
412
293
  result = False
413
- container.mount(Center(Static(str(e), classes="form-error")))
294
+ container.mount(Center(Static(str(e)), classes="form-error"))
414
295
 
415
296
  return result
416
297
 
@@ -422,12 +303,24 @@ class Form(BaseForm, metaclass=FormMetaclass):
422
303
 
423
304
  class Submitted(Message):
424
305
  """Posted when form is submitted successfully"""
425
- def __init__(self, r_form: RenderedForm):
306
+ def __init__(self, layout):
307
+ """
308
+ Args:
309
+ layout: The FormLayout instance that submitted the form
310
+ """
426
311
  super().__init__()
427
- self.form = r_form
312
+ self.layout = layout
313
+ # Provide backward compatibility
314
+ self.form = layout
428
315
 
429
316
  class Cancelled(Message):
430
317
  """Posted when form is cancelled"""
431
- def __init__(self, r_form: RenderedForm):
318
+ def __init__(self, layout):
319
+ """
320
+ Args:
321
+ layout: The FormLayout instance that cancelled the form
322
+ """
432
323
  super().__init__()
433
- self.form = r_form
324
+ self.layout = layout
325
+ # Provide backward compatibility
326
+ self.form = layout
textual_wtf/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.8.0"
1
+ __version__ = "0.8.1"
textual_wtf/widgets.py CHANGED
@@ -4,6 +4,8 @@ from textual.widgets import Input, Checkbox, Select, TextArea, Static
4
4
  from textual.containers import Center
5
5
  from textual.validation import ValidationResult, Validator
6
6
 
7
+ import wingdbstub
8
+
7
9
  if TYPE_CHECKING:
8
10
  from .fields import Field
9
11
 
@@ -21,19 +23,21 @@ _id_gen = widget_id_gen()
21
23
 
22
24
  class FormWidgetMixin:
23
25
  """Mixin to add validation error display to widgets"""
24
-
26
+
25
27
  async def on_input_changed(self, event):
26
28
  """Handle input changes and display validation errors"""
27
29
  if not hasattr(self, 'parent') or self.parent is None:
28
30
  return
29
-
31
+
30
32
  container = self.parent
31
-
33
+
32
34
  # ALWAYS clear previous errors first
33
35
  await container.remove_children(".form-error")
34
-
35
- if hasattr(event, 'validation_result') and event.validation_result is not None:
36
- vr = event.validation_result
36
+
37
+ if (
38
+ hasattr(event, 'validation_result')
39
+ and (vr := event.validation_result) is not None
40
+ ):
37
41
  if not vr.is_valid:
38
42
  # Show all errors from this validation
39
43
  for msg in vr.failure_descriptions:
@@ -42,7 +46,7 @@ class FormWidgetMixin:
42
46
 
43
47
  class FormInput(Input, FormWidgetMixin):
44
48
  """Text input widget for forms"""
45
-
49
+
46
50
  def __init__(self, *, field: Optional['Field'] = None, valid_empty: bool = True,
47
51
  validators: Optional[List[Validator]] = None, **kwargs):
48
52
  kwargs.setdefault('id', next(_id_gen))
@@ -54,7 +58,7 @@ class FormInput(Input, FormWidgetMixin):
54
58
 
55
59
  class FormIntegerInput(Input, FormWidgetMixin):
56
60
  """Integer input widget for forms"""
57
-
61
+
58
62
  def __init__(self, *, field: Optional['Field'] = None, valid_empty: bool = True,
59
63
  validators: Optional[List[Validator]] = None, **kwargs):
60
64
  kwargs.setdefault('id', next(_id_gen))
@@ -67,22 +71,22 @@ class FormIntegerInput(Input, FormWidgetMixin):
67
71
 
68
72
  class FormTextArea(TextArea, FormWidgetMixin):
69
73
  """Multi-line text area widget for forms"""
70
-
74
+
71
75
  def __init__(self, *, field: Optional['Field'] = None, text: str = "", **kwargs):
72
76
  kwargs.setdefault('id', next(_id_gen))
73
77
  super().__init__(text=text, **kwargs)
74
78
  self.field = field
75
-
79
+
76
80
  @property
77
81
  def value(self):
78
82
  """Get text area value"""
79
83
  return self.text
80
-
84
+
81
85
  @value.setter
82
86
  def value(self, v):
83
87
  """Set text area value"""
84
88
  self.text = v if v is not None else ""
85
-
89
+
86
90
  def validate(self, value):
87
91
  """Validate text area value"""
88
92
  return ValidationResult()
@@ -90,14 +94,14 @@ class FormTextArea(TextArea, FormWidgetMixin):
90
94
 
91
95
  class FormCheckbox(Checkbox):
92
96
  """Checkbox widget for forms"""
93
-
97
+
94
98
  def __init__(self, *, field: Optional['Field'] = None, label: str = "", **kwargs):
95
99
  kwargs.setdefault('id', next(_id_gen))
96
100
  super().__init__(value=False, **kwargs)
97
101
  self.field = field
98
102
  if label:
99
103
  self.label = label
100
-
104
+
101
105
  def validate(self, value):
102
106
  """Validate checkbox value"""
103
107
  return ValidationResult()
@@ -105,27 +109,27 @@ class FormCheckbox(Checkbox):
105
109
 
106
110
  class AlwaysValid(Validator):
107
111
  """Validator that always passes"""
108
-
112
+
109
113
  def validate(self, value):
110
114
  return self.success()
111
115
 
112
116
 
113
117
  class FormSelect(Select):
114
118
  """Select dropdown widget for forms"""
115
-
116
- def __init__(self, *, field: Optional['Field'] = None,
119
+
120
+ def __init__(self, *, field: Optional['Field'] = None,
117
121
  choices: List[Tuple[str, str]], allow_blank: bool = False,
118
122
  prompt: str = "Select an option", **kwargs):
119
123
  kwargs.setdefault('id', next(_id_gen))
120
124
  kwargs.setdefault('prompt', prompt)
121
-
125
+
122
126
  # Convert tuples to Select.Option objects
123
127
  options = [(label, value) for value, label in choices]
124
-
128
+
125
129
  super().__init__(options=options, **kwargs)
126
130
  self.field = field
127
131
  self.allow_blank = allow_blank
128
-
132
+
129
133
  def validate(self, value):
130
134
  """Validate select value"""
131
135
  if value != Select.BLANK or self.allow_blank:
@@ -135,9 +139,9 @@ class FormSelect(Select):
135
139
 
136
140
  class WidgetRegistry:
137
141
  """Registry for custom widgets"""
138
-
142
+
139
143
  _widgets = {}
140
-
144
+
141
145
  @classmethod
142
146
  def register(cls, name: str):
143
147
  """Decorator to register a widget"""
@@ -145,12 +149,12 @@ class WidgetRegistry:
145
149
  cls._widgets[name] = widget_class
146
150
  return widget_class
147
151
  return decorator
148
-
152
+
149
153
  @classmethod
150
154
  def get(cls, name: str):
151
155
  """Get a widget by name"""
152
156
  return cls._widgets.get(name)
153
-
157
+
154
158
  @classmethod
155
159
  def list_widgets(cls):
156
160
  """List all registered widgets"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: textual-wtf
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: A declarative forms library for Textual TUI applications
5
5
  Author-email: Steve Holden <steve@holdenweb.com>
6
6
  License: MIT
@@ -0,0 +1,23 @@
1
+ textual_wtf/__init__.py,sha256=veKh7CxdNS0DruiCW_qgOmfcSQBj-307T4h9jhw4X3M,1453
2
+ textual_wtf/bound_fields.py,sha256=2Mnhx5Yb-D-skpfH4SSJX7PH_23bTZ38fgbwM9Zg-2U,10322
3
+ textual_wtf/exceptions.py,sha256=GYef5rI0ygqXVHvBO5lrV_Ltu78FPkNvAyRSnFXe2vY,594
4
+ textual_wtf/fields.py,sha256=IVigknWKnWnjGrdZJQWVgWIbeb4S33SaJMzvks8GRcA,9045
5
+ textual_wtf/forms.py,sha256=PAHtQ-8M9omRF3YhEmuJEdvcrkXwZai6hff4Ut3jbrU,11652
6
+ textual_wtf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ textual_wtf/validators.py,sha256=635-JKuC9N0GAvy7No-mID_HtcvNvhKDBBI6FW4W0sQ,1192
8
+ textual_wtf/version.py,sha256=Ocl79hbbH8_jdr5dGC90VR1cAvZc05Rc0tkZttUnMjo,22
9
+ textual_wtf/widgets.py,sha256=4RtcDPefQmPJ5JbQXWS8MsXJ5p6Ex4ENNcWw87z8o8s,5047
10
+ textual_wtf/demo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ textual_wtf/demo/advanced_form.py,sha256=RjJA3VbAxvdnBnMsQq4LpLvDnYJrDBR4j3lnlaJukpM,3614
12
+ textual_wtf/demo/basic_form.py,sha256=9hD5NSTGMuoKlogmm3H4OGiGrJJKAmGdLbPYykdwgeA,2660
13
+ textual_wtf/demo/basic_form.tcss,sha256=bRKdPZyIC1mLjfkmQiwcc3JwzcSDWI3EeLs42JeneSw,71
14
+ textual_wtf/demo/launcher.py,sha256=41DOguB7Dism9zav3NTBMJhtvkmi3bpfeocM3MvoPdk,3306
15
+ textual_wtf/demo/nested_once_form.py,sha256=ZsL1N6B_PhbOcDtw-4L76R-6J-q_U4rCZaM63fPGZ6E,5046
16
+ textual_wtf/demo/nested_twice_form.py,sha256=TvDnbIH6pzuGoR6yGzI7a0FZGNTyKk8D3_mQO7qx2aU,5073
17
+ textual_wtf/demo/results_screen.py,sha256=3DsL-WRXA9x9AdpT6r3HD3-snlEk9kzy_XwB7O4FsqU,2035
18
+ textual_wtf/demo/user_registration.py,sha256=gTs9kmtzALYIOu5rm7HMRlifWIYjmfPAUYG_jFvYr3s,3550
19
+ textual_wtf-0.8.1.dist-info/METADATA,sha256=l9gMOMfIPoAEpAP50tcJ6r_0DrcwlX-P3Tov207bYMw,8578
20
+ textual_wtf-0.8.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
21
+ textual_wtf-0.8.1.dist-info/entry_points.txt,sha256=jaJ4-T5M_61CTqJGCnRwAv6T7fVhWDzBaLKtTnoBbSM,63
22
+ textual_wtf-0.8.1.dist-info/licenses/LICENSE,sha256=OD-OyOXFRBQ9lM4Z-XGnP_WUfVObajlPYdVVP3c_7OM,1089
23
+ textual_wtf-0.8.1.dist-info/RECORD,,
@@ -1,23 +0,0 @@
1
- textual_wtf/__init__.py,sha256=lo3oT72caUN0tiVmkVLY5JXGEisUwxA_xYPhgfdGzkQ,1345
2
- textual_wtf/bound_fields.py,sha256=Jk5o81dRlvuGivR_XdtjOo7_SE-J8HuEVdrJrZhimG8,8570
3
- textual_wtf/exceptions.py,sha256=GYef5rI0ygqXVHvBO5lrV_Ltu78FPkNvAyRSnFXe2vY,594
4
- textual_wtf/fields.py,sha256=gODdtGJySuBycCeiUKvvU2k1nLRAdPMvALsrqKz1oSA,8782
5
- textual_wtf/forms.py,sha256=tAXvf2p7OD2unBMbBA_uaOr2_6jzuMRzjVgfT_WlOco,14335
6
- textual_wtf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- textual_wtf/validators.py,sha256=635-JKuC9N0GAvy7No-mID_HtcvNvhKDBBI6FW4W0sQ,1192
8
- textual_wtf/version.py,sha256=iPlYCcIzuzW7T2HKDkmYlMkRI51dBLfNRxPPiWrfw9U,22
9
- textual_wtf/widgets.py,sha256=d5a0DiTJtdlQsQkeTvhEsHLDYwqEVn_zC6v8DzyN3NI,5130
10
- textual_wtf/demo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- textual_wtf/demo/advanced_form.py,sha256=sK9aRE8fp3OtDiWf9c3qpIMKz4pIjCpy5SE5J6o7t0g,3604
12
- textual_wtf/demo/basic_form.py,sha256=x9RiosmkzDC0mYPdEMiGlRFqX0O9C5dJVyw1XdXdcpo,2655
13
- textual_wtf/demo/basic_form.tcss,sha256=bRKdPZyIC1mLjfkmQiwcc3JwzcSDWI3EeLs42JeneSw,71
14
- textual_wtf/demo/launcher.py,sha256=41DOguB7Dism9zav3NTBMJhtvkmi3bpfeocM3MvoPdk,3306
15
- textual_wtf/demo/nested_once_form.py,sha256=Ta_86TDYgwg8QOjVyVgaRMKjPHAC4b64AR9ymURLR3M,5036
16
- textual_wtf/demo/nested_twice_form.py,sha256=JDSDaO03p4p8tl562pNTnXXPN3Q5fDTqOvZ3dZ1L0SY,5063
17
- textual_wtf/demo/results_screen.py,sha256=3DsL-WRXA9x9AdpT6r3HD3-snlEk9kzy_XwB7O4FsqU,2035
18
- textual_wtf/demo/user_registration.py,sha256=LxyetdJmVs3G0ZoUr8PdGmFo-GEVPleCxdZZfEVAnJw,3540
19
- textual_wtf-0.8.0.dist-info/METADATA,sha256=-IQ6VDcCKUpsGHo4mTE-ahcHmGBQ-UcftPzuVUSi-bE,8578
20
- textual_wtf-0.8.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
21
- textual_wtf-0.8.0.dist-info/entry_points.txt,sha256=jaJ4-T5M_61CTqJGCnRwAv6T7fVhWDzBaLKtTnoBbSM,63
22
- textual_wtf-0.8.0.dist-info/licenses/LICENSE,sha256=OD-OyOXFRBQ9lM4Z-XGnP_WUfVObajlPYdVVP3c_7OM,1089
23
- textual_wtf-0.8.0.dist-info/RECORD,,