Repository navigation
Expand file tree
/
Copy pathbase.go
More file actions
251 lines (214 loc) · 7.06 KB
/
Copy pathbase.go
File metadata and controls
251 lines (214 loc) · 7.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package input
import (
"webtyp.com/fmt"
"webtyp.com/lang"
"webtyp.com/model"
)
// Base contains common logic and fields (State) for all inputs.
// It is intended to be embedded in concrete input structs.
type Base struct {
id string
name string
htmlName string // The HTML type (e.g., "text", "email")
Values []string // Multiple values support (for select/checkbox/etc)
Options []fmt.KeyValue // Multiple options for select/checkbox/etc
Placeholder []string // raw, untranslated words; resolved live by GetPlaceholder
Example string // sample format data (never translated, appended to placeholder)
Title string
Required bool // HTML required attribute
Disabled bool // HTML disabled attribute
Readonly bool // HTML readonly attribute
SkipValidation bool // Whether to skip validation for this input
// Masked renders the control as <input type="password"> without changing
// what the value IS or how it validates — presentation, not data (the
// Django Field/Widget split; here as a boolean over the same input,
// Flutter's obscureText style, since input.Input fuses Field and Widget).
Masked bool
FullWidth bool // Controls whether the field spans all columns in multi-column layouts
Attributes []fmt.KeyValue
model.Permitted // anonymous embed: promotes Letters, Numbers, Validate(), etc.
}
// InitBase initializes the base fields and constructs the unique ID.
func (b *Base) InitBase(parentID, name, htmlName string) {
if parentID != "" {
b.id = parentID + "." + name
} else {
b.id = name
}
b.name = name
b.htmlName = htmlName
// Only apply defaults if not already set (preserves values during Clone).
// Placeholder is NOT defaulted to name: a host UI (e.g. components/fieldset)
// already shows the field name as a label chip, so a placeholder mirroring
// it would just repeat the same text with no added information. Leave it
// empty unless a widget explicitly sets one for a real example value (see
// input/address.go, input/rut.go).
if b.Title == "" {
b.Title = name
}
}
// SetValues sets the input values.
func (b *Base) SetValues(v ...string) {
b.Values = v
}
// GetValue returns the first value (for simple inputs).
func (b *Base) GetValue() string {
if len(b.Values) > 0 {
return b.Values[0]
}
return ""
}
// GetValues returns all input values.
func (b *Base) GetValues() []string {
return b.Values
}
// SetPlaceholder sets the input placeholder's translatable words.
func (b *Base) SetPlaceholder(ph ...string) {
b.Placeholder = ph
}
// SetExample sets a non-translatable format sample to append to the placeholder.
func (b *Base) SetExample(sample string) {
b.Example = sample
}
// GetPlaceholder returns the input placeholder, translated for the
// currently active language (resolved live — see fmt/lang.OutLang).
func (b *Base) GetPlaceholder() string {
var ph string
if len(b.Placeholder) > 0 {
args := make([]any, len(b.Placeholder))
for i, p := range b.Placeholder {
args[i] = p
}
ph = lang.Translate(args...).String()
}
if b.Example != "" {
if ph != "" {
return ph + " " + b.Example
}
return b.Example
}
return ph
}
// SetTitle sets the input title (tooltip).
func (b *Base) SetTitle(title string) {
b.Title = title
}
// GetTitle returns the input title.
func (b *Base) GetTitle() string {
return b.Title
}
// SetSkipValidation sets whether to skip validation for this input.
func (b *Base) SetSkipValidation(skip bool) {
b.SkipValidation = skip
}
// GetSkipValidation returns whether to skip validation.
func (b *Base) GetSkipValidation() bool {
return b.SkipValidation
}
// SetMasked sets whether this input renders as <input type="password">.
func (b *Base) SetMasked(v bool) { b.Masked = v }
// IsMasked returns whether this input renders as <input type="password">.
func (b *Base) IsMasked() bool { return b.Masked }
// SetOptions sets multiple options (for select/checkbox/etc).
func (b *Base) SetOptions(opts ...fmt.KeyValue) {
b.Options = opts
}
// GetOptions returns all options.
func (b *Base) GetOptions() []fmt.KeyValue {
return b.Options
}
// GetSelectedValue returns the first value in Values, or empty if none.
func (b *Base) GetSelectedValue() string {
return b.GetValue()
}
// GetID returns the component's unique identifier.
func (b *Base) GetID() string {
return b.id
}
// ErrorID returns the ID of the associated error span for this input.
func (b *Base) ErrorID() string { return b.id + ".error" }
// SetID sets the component's unique identifier.
func (b *Base) SetID(id string) {
b.id = id
}
// Name satisfies model.Kind.Name(). Returns the semantic input type name.
func (b *Base) Name() string { return b.htmlName }
// Storage satisfies model.Kind.Storage().
func (b *Base) Storage() model.FieldType {
switch b.htmlName {
case "number":
return model.FieldInt
case "checkbox":
return model.FieldBool
default:
return model.FieldText
}
}
// Validate satisfies model.Kind.Validate().
// Concrete structs embedding Base can override this to provide specialized validation.
// When Options are declared the widget is a CLOSED enum: the value must match one of the
// option Keys exactly (an exact key match needs no further charset validation — the option
// list IS the whitelist). Empty + not Required passes: absence is handled by Required, it is
// not an invalid choice.
func (b *Base) Validate(value string) error {
if value == "" && b.Required {
return fmt.Err("field", b.name, "is required")
}
if len(b.Options) > 0 {
if value == "" {
return nil
}
for _, opt := range b.Options {
if opt.Key == value {
return nil
}
}
return fmt.Err("Value", value, "NotAllowed", "in", b.name)
}
return b.Permitted.Validate(b.name, value)
}
// SetRequired sets the required attribute.
func (b *Base) SetRequired(req bool) {
b.Required = req
}
// IsRequired returns true if the input is required.
func (b *Base) IsRequired() bool {
return b.Required
}
// IsDisabled returns true if the input is disabled.
func (b *Base) IsDisabled() bool {
return b.Disabled
}
// IsReadonly returns true if the input is readonly.
func (b *Base) IsReadonly() bool {
return b.Readonly
}
// GetAttributes returns custom attributes.
func (b *Base) GetAttributes() []fmt.KeyValue {
return b.Attributes
}
// HandlerName returns the component's unique identifier.
// Deprecated: use GetID instead.
func (b *Base) HandlerName() string {
return b.id
}
// FieldName returns the struct field name (without parent prefix).
func (b *Base) FieldName() string {
return b.name
}
// HTMLName returns the HTML input type.
func (b *Base) HTMLName() string {
return b.htmlName
}
// AddAttribute adds a custom attribute to the input.
func (b *Base) AddAttribute(key, value string) {
b.Attributes = append(b.Attributes, fmt.KeyValue{Key: key, Value: value})
}
// IsFullWidth returns true if this input requests full container width in a grid.
func (b *Base) IsFullWidth() bool {
return b.FullWidth
}
// SetFullWidth sets whether this input requests full container width.
func (b *Base) SetFullWidth(v bool) {
b.FullWidth = v
}